DopingControl 0.7.0
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
-- DopingControl data/classbuffs.lua
|
||||
-- Class-buffs tab:
|
||||
-- slot definitions + buff name -> slot table with rank spell IDs.
|
||||
--
|
||||
-- [ASSUMPTION] Rank IDs below are from vanilla 1.12 knowledge and are
|
||||
-- not individually verified in-game. Verify via
|
||||
-- the probe dump before trusting ID matching; TWoW may add extra ranks.
|
||||
-- Name-based detection alone is sufficient for tab purposes EXCEPT the
|
||||
-- "Shadow Protection" name collision: the priest buff and the old
|
||||
-- protection-potion aura (7242) share the name -> ID-before-name matching
|
||||
-- is mandatory once both tabs are live.
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Slot definitions (array, order = column order). Same field shape as
|
||||
-- DC.SLOTS_CONSUMABLES. Slot ids: AI/MOTW/PWF/SPROT/EMB/SOUL;
|
||||
-- tile labels/sub-labels: INT/MTW/FRT/SP/EMB/SST. EMB (Emerald Blessing)
|
||||
-- carries NO seed entry in data/expectations.lua -- see that file's
|
||||
-- header for why -- so it starts as an inactive dynamic column, exactly
|
||||
-- like the equipment tab's T1/T2.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.SLOTS_CLASSBUFFS = {
|
||||
{ id = "AI", label = "INT", sub = "Arcane", full = "Arcane Intellect", kind = "aura",
|
||||
items = "Arcane Intellect / Brilliance (mage)" },
|
||||
{ id = "MOTW", label = "MTW", sub = "Wild", full = "Mark of the Wild", kind = "aura",
|
||||
items = "Mark / Gift of the Wild (druid)" },
|
||||
{ id = "PWF", label = "FRT", sub = "Priest", full = "Power Word: Fortitude", kind = "aura",
|
||||
items = "Power Word / Prayer of Fortitude (priest)" },
|
||||
{ id = "SPROT", label = "SP", sub = "situat.", full = "Shadow Protection", kind = "aura",
|
||||
items = "Shadow Protection (priest) - situational per boss" },
|
||||
{ id = "EMB", label = "EMB", sub = "hit", full = "Emerald Blessing", kind = "aura",
|
||||
items = "Emerald Blessing (druid, Emerald Sanctum questline) - also grants +1% spell hit, see Resistances" },
|
||||
{ id = "SOUL", label = "SST", sub = "healers", full = "Soulstone", kind = "aura",
|
||||
items = "Soulstone (warlock) - expected only for healers" },
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Buff name -> slot + rank IDs ([ASSUMPTION], see header).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.CLASSBUFFS = {
|
||||
["Arcane Intellect"] = { slot = "AI", ids = { 1459, 1460, 1461, 10156, 10157 } },
|
||||
["Arcane Brilliance"] = { slot = "AI", ids = { 23028 } },
|
||||
["Mark of the Wild"] = { slot = "MOTW", ids = { 1126, 5232, 6756, 5234, 8907, 9884, 9885 } },
|
||||
["Gift of the Wild"] = { slot = "MOTW", ids = { 21849, 21850 } },
|
||||
["Power Word: Fortitude"] = { slot = "PWF", ids = { 1243, 1244, 1245, 2791, 10937, 10938 } },
|
||||
["Prayer of Fortitude"] = { slot = "PWF", ids = { 21562, 21564 } },
|
||||
["Shadow Protection"] = { slot = "SPROT", ids = { 976, 10957, 10958 } }, -- name collides with potion aura 7242
|
||||
["Prayer of Shadow Protection"] = { slot = "SPROT", ids = { 27683 } },
|
||||
-- raid-wide druid buff on this server; both spell ids seen for it
|
||||
["Emerald Blessing"] = { slot = "EMB", ids = { 57108, 57109 } },
|
||||
["Soulstone Resurrection"] = { slot = "SOUL", ids = { 20707, 20762, 20763, 20764, 20765, 20766 } },
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Merge rank IDs into the global spellID -> slotId map started by
|
||||
-- data/consumables.lua (slot ids are unique across tabs -> no ambiguity;
|
||||
-- the ID sets are disjoint, test_data.lua asserts this).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.SPELL_TO_SLOT = DC.SPELL_TO_SLOT or {}
|
||||
for name, def in pairs(DC.CLASSBUFFS) do
|
||||
local n = table.getn(def.ids)
|
||||
for i = 1, n do
|
||||
DC.SPELL_TO_SLOT[def.ids[i]] = def.slot
|
||||
end
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Name-only aura collisions with CONSUMABLE slots (decision 2026-08-15):
|
||||
-- a name-only aura match (no spell ID at all for that scan) must NEVER
|
||||
-- satisfy a CONSUMABLE slot when the matched display name is also a class
|
||||
-- buff name -- the scan cannot tell the two apart, and defaulting to HAS
|
||||
-- would hide a missing consumable behind someone else's class buff. Such
|
||||
-- a slot classifies UNKNOWN instead (core/model.lua's aura name-fallback
|
||||
-- branch consults this table; core/const.lua's Iron Rule: UNKNOWN is
|
||||
-- never collapsed into HAS or MISSING).
|
||||
--
|
||||
-- Scope is deliberately ONE-SIDED: only the CONSUMABLE slot side of the
|
||||
-- collision is affected. The class-buff slot (e.g. SPROT) keeps the
|
||||
-- existing name-fallback behavior unchanged -- the ambiguity here means
|
||||
-- "we can't prove the CONSUMABLE was used", not "we can't prove the class
|
||||
-- buff is missing". Do not widen this into "name-only never counts"; that
|
||||
-- would make every ID-less scan MISSING/UNKNOWN across the whole matrix.
|
||||
--
|
||||
-- Keyed by buff display name (a key shared by DC.CONSUMABLES and
|
||||
-- DC.CLASSBUFFS). test_data.lua asserts every entry here is a key in BOTH
|
||||
-- tables, so a stale or speculative entry cannot ship silently.
|
||||
-- "Shadow Protection" -- DC.CONSUMABLES["Shadow Protection"] (SR slot,
|
||||
-- Shadow Protection Potion, spell 7242, data/consumables.lua) vs.
|
||||
-- DC.CLASSBUFFS["Shadow Protection"] above (SPROT slot, priest buff,
|
||||
-- ids 976/10957/10958) -- identical display name, already flagged as a
|
||||
-- collision at both definitions and wowhead-confirmed 2026-08-11 (see
|
||||
-- the SR entry's comment in data/consumables.lua). This is the ONLY
|
||||
-- name shared between DC.CONSUMABLES and DC.CLASSBUFFS (checked against
|
||||
-- every key in both tables) -- no other entry is added without the same
|
||||
-- level of evidence.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.CONSUMABLE_CLASSBUFF_NAME_COLLISION = {
|
||||
["Shadow Protection"] = true,
|
||||
}
|
||||
@@ -0,0 +1,369 @@
|
||||
-- DopingControl data/consumables.lua
|
||||
-- Consumables tab: slot definitions + buff->entry table + spellID->slot map
|
||||
-- + the pure item-identification ladder (DC.ResolveItem).
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
--
|
||||
-- SLOT MODEL (v6, 22 slots -- the 17-slot base design plus the five school
|
||||
-- protection-potion columns FrR/NR/SR/HR/AR added 2026-08-11):
|
||||
-- One column = one thing that can be active independently of every other
|
||||
-- column; items competing INSIDE a column are the "pick one" alternatives.
|
||||
-- Source: community-sourced TurtleWoW 1.17.2 consumable-stacking rules,
|
||||
-- trust external -- vanilla 1.12 slot rules, no TBC
|
||||
-- Battle/Guardian categories. The old 11-slot model conflated four
|
||||
-- independent slots into SP (GAE / school elixir / Dreamtonic / Dreamshard
|
||||
-- are simultaneously active in the caster set) and two into HP (Fortitude
|
||||
-- + stamina alcohol stack), and lacked BL/ZANZA entirely.
|
||||
--
|
||||
-- Deliberate compromises (documented, not hidden):
|
||||
-- * ALC is ONE column although different alcohol types stack (Merlot +
|
||||
-- Rumsey Black Label at once; same types do not): modelled as one
|
||||
-- column showing the strongest active, detail in tooltip. The
|
||||
-- slot-collision tests exempt exactly this column.
|
||||
-- * Gift of Arthas and Juju Flurry get NO column (defense elixirs all
|
||||
-- stack; Flurry lasts 20 s).
|
||||
-- * UNVERIFIED gaps (community source does not cover them; marked
|
||||
-- `unverified = true` on the slot/entries, upgrade via dev/raiddump
|
||||
-- evidence next raid): (1) WPN main-hand/off-hand coexistence (oil on
|
||||
-- MH + stone on OH), (2) protection-potion slot rules (all six school
|
||||
-- columns FR/FrR/NR/SR/HR/AR -- cross-school stacking is NOT
|
||||
-- evidenced, so each school gets its own column but every one stays
|
||||
-- flagged unverified), (3) world-buff interactions (no world-buff
|
||||
-- columns at all).
|
||||
--
|
||||
-- Entry fields (DC.CONSUMABLES[buffName]):
|
||||
-- slot : slot id (column) the buff fills
|
||||
-- spellID : aura spell id; 0 = "match by name only" (no known ID)
|
||||
-- item : the consumable's display name (whisper/tooltip/cell)
|
||||
-- icon : BARE icon texture name (no "Interface\Icons\" prefix, no
|
||||
-- extension -- prefix is added at render time); nil when the
|
||||
-- icon could not be sourced (the matrix fallback ladder
|
||||
-- handles nil: slot-generic icon, then the green check)
|
||||
-- discrim : optional Lua 5.0 string.find pattern matched against the
|
||||
-- buff tooltip's EFFECT line (scan/aura.lua auras.effects) --
|
||||
-- only where the buff NAME is generic
|
||||
-- variants : optional ARRAY of { item, icon, discrim } for generic buff
|
||||
-- names ("Well Fed") that many different items produce; the
|
||||
-- identification ladder walks them in order, FIRST match wins
|
||||
-- unverified : optional true -- see the honesty gaps above
|
||||
--
|
||||
-- Icon provenance (2026-08-11, never guessed): wowhead classic tooltip
|
||||
-- API for the vanilla items, octowow.st/db + wowauctions for TurtleWoW
|
||||
-- customs; icon = nil where no source confirmed one. Two lookalike
|
||||
-- collisions are GENUINE, not typos: Dragonbreath Chili and Danonzo's
|
||||
-- Delight share inv_drink_17 with Nightfin Soup; Gordok Green Grog
|
||||
-- shares inv_drink_03 with Rumsey Rum; Cerebral Cortex Compound shares
|
||||
-- inv_potion_32 with the Mongoose elixir.
|
||||
--
|
||||
-- Separator decision (once for all data files): items strings are
|
||||
-- tooltip/FontString-only, so they may use the middle-dot list separator
|
||||
-- "\194\183" (UTF-8 middle dot renders fine in own FontStrings; the group
|
||||
-- headers already use the same glyph). Em dashes stay ASCII "-" (that
|
||||
-- glyph is unproven in the 1.12 font). SENT chat lines remain pure ASCII
|
||||
-- -- they never use these strings.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Slot definitions (array, order = column order).
|
||||
-- Fields: id (stable key, used by DEFAULT_EXPECT / model / UI),
|
||||
-- label (column tile abbrev), sub (tile sub-label), full (long
|
||||
-- name, used in whisper/report), kind ("aura" | "weapon"),
|
||||
-- items (tooltip example text, optional), unverified (optional,
|
||||
-- see header),
|
||||
-- icon (BARE slot-generic icon texture name -- same bare-name
|
||||
-- rule as the entry icons above; the matrix HAS-cell fallback
|
||||
-- ladder renders it when the ITEM behind a buff cannot be
|
||||
-- resolved: resolved item icon -> this slot icon -> green check.
|
||||
-- A representative member item's icon by design -- the cell must
|
||||
-- read as "this column", not as a question mark).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.SLOTS_CONSUMABLES = {
|
||||
{ id = "FLASK", label = "FLK", sub = "Flask", full = "Flask", kind = "aura",
|
||||
icon = "inv_potion_62",
|
||||
items = "Flask of the Titans \194\183 Distilled Wisdom \194\183 Supreme Power" },
|
||||
{ id = "FOOD", label = "FOD", sub = "Food", full = "Food buff", kind = "aura",
|
||||
icon = "inv_misc_food_15",
|
||||
items = "one food buff at a time - e.g. Smoked Desert Dumplings \194\183 Danonzo's Tel'Abim foods" },
|
||||
{ id = "AP", label = "AP", sub = "Attack", full = "Attack power", kind = "aura",
|
||||
icon = "inv_potion_92",
|
||||
items = "Winterfall Firewater \194\183 Juju Might" },
|
||||
{ id = "STR", label = "STR", sub = "Str.", full = "Strength", kind = "aura",
|
||||
icon = "inv_potion_61",
|
||||
items = "Juju Power \194\183 Elixir of Giants" },
|
||||
{ id = "AGI", label = "AGI", sub = "Agi.", full = "Agility", kind = "aura",
|
||||
icon = "inv_potion_32",
|
||||
items = "Elixir of the Mongoose - free-stacker, own column" },
|
||||
{ id = "BL", label = "BL", sub = "Blasted", full = "Blasted Lands buff", kind = "aura",
|
||||
icon = "inv_stone_15",
|
||||
items = "R.O.I.D.S. \194\183 Ground Scorpok Assay \194\183 Lung Juice Cocktail \194\183 Cortex Compound \194\183 Gizzard Gum" },
|
||||
{ id = "ZANZA", label = "ZAN", sub = "Zanza", full = "Spirit of Zanza", kind = "aura",
|
||||
icon = "inv_potion_30",
|
||||
items = "Spirit of Zanza - free-stacker, one Zanza effect at a time" },
|
||||
{ id = "GAE", label = "GAE", sub = "Arcane", full = "Greater Arcane Elixir", kind = "aura",
|
||||
icon = "inv_potion_25",
|
||||
items = "Greater Arcane Elixir \194\183 Arcane Elixir" },
|
||||
{ id = "SCHOOL", label = "SCH", sub = "School", full = "School elixir", kind = "aura",
|
||||
icon = "inv_potion_46",
|
||||
items = "Shadow Power \194\183 Frost Power \194\183 Greater Firepower - one per school slot" },
|
||||
{ id = "DREAMT", label = "DRT", sub = "Dreamt.", full = "Dreamtonic", kind = "aura",
|
||||
icon = "inv_potion_10",
|
||||
items = "Dreamtonic (TurtleWoW custom)" },
|
||||
{ id = "SHARD", label = "SHD", sub = "Shard", full = "Dreamshard Elixir", kind = "aura",
|
||||
icon = "inv_potion_12",
|
||||
items = "Dreamshard Elixir (TurtleWoW custom)" },
|
||||
{ id = "MP5", label = "MP5", sub = "Mana", full = "Mana regen", kind = "aura",
|
||||
icon = "inv_potion_45",
|
||||
items = "Mageblood Potion - Nightfin Soup is a FOOD buff, not this column" },
|
||||
{ id = "ARM", label = "ARM", sub = "Armor", full = "Armor", kind = "aura",
|
||||
icon = "inv_potion_66",
|
||||
items = "Elixir of Superior Defense" },
|
||||
{ id = "HPELX", label = "FRT", sub = "Fort.", full = "Elixir of Fortitude", kind = "aura",
|
||||
icon = "inv_potion_43",
|
||||
items = "Elixir of Fortitude - stacks with the alcohol column" },
|
||||
{ id = "ALC", label = "ALC", sub = "Alcohol", full = "Alcohol buff", kind = "aura",
|
||||
icon = "inv_drink_04",
|
||||
items = "Medivh's Merlot \194\183 Rumsey Rum Black Label \194\183 Kreeg's Stout Beatdown - different types stack, same type does not; one column, strongest shown" },
|
||||
{ id = "FR", label = "FR", sub = "fire", full = "Fire protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_24",
|
||||
items = "Greater Fire Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
-- The remaining school protection columns follow the FR pattern (one
|
||||
-- column per school, cross-school stacking unverified -- see header).
|
||||
-- Unlike FR they carry NO seed entry in data/expectations.lua: the
|
||||
-- dynamic-column rule keeps them hidden until enabled per role/class.
|
||||
{ id = "FrR", label = "FrR", sub = "frost", full = "Frost protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_20",
|
||||
items = "Greater Frost Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "NR", label = "NR", sub = "nature", full = "Nature protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_22",
|
||||
items = "Greater Nature Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "SR", label = "SR", sub = "shadow", full = "Shadow protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_23",
|
||||
items = "Greater Shadow Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "HR", label = "HR", sub = "holy", full = "Holy protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_09",
|
||||
items = "Holy Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "AR", label = "AR", sub = "arcane", full = "Arcane protection", kind = "aura", unverified = true,
|
||||
icon = "inv_potion_83",
|
||||
items = "Greater Arcane Protection Potion - situational per boss; enable/disable via the expectation matrix" },
|
||||
{ id = "WPN", label = "WPN", sub = "temp.", full = "Weapon (temporary)", kind = "weapon", unverified = true, -- NOT an aura slot; MH/OH split unverified
|
||||
icon = "inv_stone_sharpeningstone_01",
|
||||
items = "Elemental/Dense Sharpening Stone \194\183 Brilliant Wizard/Mana Oil - permanent enchant: Equipment tab" },
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Buff name -> entry table.
|
||||
-- spellID = 0 means "match by name only" (no known aura spell ID).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.CONSUMABLES = {
|
||||
-- FLASK
|
||||
["Flask of the Titans"] = { slot = "FLASK", spellID = 17626, item = "Flask of the Titans", icon = "inv_potion_62" },
|
||||
["Flask of Supreme Power"] = { slot = "FLASK", spellID = 17628, item = "Flask of Supreme Power", icon = "inv_potion_41" },
|
||||
["Flask of Distilled Wisdom"] = { slot = "FLASK", spellID = 17627, item = "Flask of Distilled Wisdom", icon = "inv_potion_97" },
|
||||
["Flask of Chromatic Resistance"] = { slot = "FLASK", spellID = 17629, item = "Flask of Chromatic Resistance", icon = "inv_potion_48" },
|
||||
["Flask of Petrification"] = { slot = "FLASK", spellID = 17624, item = "Flask of Petrification", icon = "inv_potion_26" }, -- NOT inv_potion_98 (a often-repeated wrong guess) -- wowhead-confirmed
|
||||
|
||||
-- FOOD (buff names, not item names).
|
||||
-- "Well Fed" is the generic food aura: the ITEM is identified through
|
||||
-- the tooltip EFFECT line (scan/aura.lua auras.effects) against the
|
||||
-- per-variant discrim patterns below -- first match wins, no match =
|
||||
-- nil = the matrix falls back to the slot-generic icon ("food buff,
|
||||
-- unknown which"). The variant list is the food set of the
|
||||
-- reference stacking rules plus the classic raid staples; the discrim
|
||||
-- patterns are best-effort data (recorded-tooltip verification via
|
||||
-- dev/raiddump pending) -- a wrong one is a one-line fix.
|
||||
-- ["Food"] (the eating tick) deliberately REMOVED: it is the
|
||||
-- sit-and-eat channel aura, not a food buff -- it made the FOOD cell
|
||||
-- green for anyone currently chewing (field-tested). Only lasting
|
||||
-- food buffs count.
|
||||
["Well Fed"] = { slot = "FOOD", spellID = 0, item = "food buff", icon = nil,
|
||||
variants = {
|
||||
-- ORDER MATTERS: first match wins, so specific effect lines
|
||||
-- (crit, ranged AP, spell damage) sit ABOVE the generic stat
|
||||
-- words they could otherwise be swallowed by.
|
||||
-- Several Turtle customs SHARE their aura spell with a classic
|
||||
-- staple (icon research 2026-08-11: Power Mushroom = 24800 =
|
||||
-- Dumplings, Sour Mountain Berry = 18230 = Grilled Squid,
|
||||
-- Juicy Striped Melon = 22731 = Runn Tum Tuber) -- those pairs
|
||||
-- are indistinguishable by effect line; the listed-first item
|
||||
-- is shown as the representative. Same slot, so harmless.
|
||||
{ item = "Danonzo's Tel'Abim Delight", icon = "inv_drink_17", discrim = "[Ss]pell [Dd]amage" },
|
||||
{ item = "Danonzo's Tel'Abim Medley", icon = "inv_misc_food_08", discrim = "[Hh]aste" },
|
||||
{ item = "Danonzo's Tel'Abim Surprise", icon = "inv_misc_food_09", discrim = "[Rr]anged [Aa]ttack [Pp]ower" },
|
||||
{ item = "Gurubashi Gumbo", icon = "inv_misc_food_64", discrim = "[Cc]rit" },
|
||||
{ item = "Le Fishe Au Chocolat", icon = nil, discrim = "[Dd]odge" }, -- icon: questionmark placeholder even upstream (octowow + wowauctions), stays nil
|
||||
{ item = "Empowering Herbal Salad", icon = "inv_misc_food_salad", discrim = "[Hh]ealing" },
|
||||
{ item = "Nightfin Soup", icon = "inv_drink_17", discrim = "8 [Mm]ana" },
|
||||
{ item = "Sagefish Delight", icon = "inv_misc_fish_21", discrim = "6 [Mm]ana" },
|
||||
-- +20 Strength: Dumplings and Power Mushroom, same spell 24800
|
||||
{ item = "Smoked Desert Dumplings", icon = "inv_misc_food_64", discrim = "[Ss]trength" },
|
||||
{ item = "Power Mushroom", icon = "inv_mushroom_11", discrim = "[Ss]trength" }, -- NOT "+10 all stats" (dead pattern until 2026-08-11); tooltip-verified +20 STR
|
||||
-- +10 Agility: Grilled Squid and Sour Mountain Berry, same spell 18230
|
||||
{ item = "Grilled Squid", icon = "inv_misc_fish_13", discrim = "[Aa]gility" },
|
||||
{ item = "Sour Mountain Berry", icon = "inv_misc_food_40", discrim = "[Aa]gility" },
|
||||
-- +10 Intellect: classic Tuber and Turtle Melon, same spell 22731
|
||||
{ item = "Runn Tum Tuber Surprise", icon = "inv_misc_food_63", discrim = "[Ii]ntellect" },
|
||||
{ item = "Juicy Striped Melon", icon = "inv_misc_food_22", discrim = "[Ii]ntellect" },
|
||||
-- generic stamina LAST: Gumbo's line also contains Stamina and
|
||||
-- must be caught by its crit pattern above, never by this one
|
||||
{ item = "Hardened Mushroom", icon = "inv_mushroom_11", discrim = "[Ss]tamina" }, -- NOT "+armor" (dead pattern until 2026-08-11); tooltip-verified +25 STA, spell 25660
|
||||
} },
|
||||
["Increased Stamina"] = { slot = "FOOD", spellID = 0, item = "stamina food", icon = nil },
|
||||
["Dragonbreath Chili"] = { slot = "FOOD", spellID = 15852, item = "Dragonbreath Chili", icon = "inv_drink_17" }, -- yes, the chili really uses a drink icon (wowhead-confirmed via og:image)
|
||||
|
||||
-- AP
|
||||
["Winterfall Firewater"] = { slot = "AP", spellID = 17038, item = "Winterfall Firewater", icon = "inv_potion_92" },
|
||||
["Juju Might"] = { slot = "AP", spellID = 16329, item = "Juju Might", icon = "inv_misc_monsterscales_07" },
|
||||
|
||||
-- STR
|
||||
["Juju Power"] = { slot = "STR", spellID = 16323, item = "Juju Power", icon = "inv_misc_monsterscales_11" },
|
||||
["Elixir of Giants"] = { slot = "STR", spellID = 11405, item = "Elixir of Giants", icon = "inv_potion_61" },
|
||||
["Elixir of the Giants"] = { slot = "STR", spellID = 11405, item = "Elixir of Giants", icon = "inv_potion_61" }, -- AURA name on this client (measured live: SpellInfo(11405) = "Elixir of the Giants"); the item is named without "the", and matching runs on the aura name first
|
||||
["Elixir of Brute Force"] = { slot = "STR", spellID = 17537, item = "Elixir of Brute Force", icon = "inv_potion_40" },
|
||||
|
||||
-- AGI
|
||||
["Elixir of the Mongoose"] = { slot = "AGI", spellID = 17538, item = "Elixir of the Mongoose", icon = "inv_potion_32" },
|
||||
["Elixir of Greater Agility"] = { slot = "AGI", spellID = 11334, item = "Elixir of Greater Agility", icon = "inv_potion_94" },
|
||||
["Greater Agility"] = { slot = "AGI", spellID = 11334, item = "Elixir of Greater Agility", icon = "inv_potion_94" }, -- AURA name (the item name never appears as a buff); was tallied as unknown 14x
|
||||
["Elixir of Agility"] = { slot = "AGI", spellID = 11328, item = "Elixir of Agility", icon = "inv_potion_93" },
|
||||
|
||||
-- BL (Blasted Lands buff slot -- one at a time, pick one; the five
|
||||
-- items were split across STR/nowhere in the old model).
|
||||
-- Aura names + spell ids wowhead-confirmed 2026-08-11 ("Strike of the
|
||||
-- Scorpok" additionally measured live in a 26-man raid). The item
|
||||
-- names ride along as alias keys (spellID 0) because the Turtle
|
||||
-- client may surface the permanent aura under the item name (reference
|
||||
-- note); a key that never matches is harmless.
|
||||
["Rage of Ages"] = { slot = "BL", spellID = 10667, item = "R.O.I.D.S.", icon = "inv_stone_15" },
|
||||
["R.O.I.D.S."] = { slot = "BL", spellID = 0, item = "R.O.I.D.S.", icon = "inv_stone_15" },
|
||||
["Strike of the Scorpok"] = { slot = "BL", spellID = 10669, item = "Ground Scorpok Assay", icon = "inv_misc_dust_02" }, -- old model filed it under STR with the aura name as item -- both wrong (it is +25 AGI, and the item is the Assay)
|
||||
["Ground Scorpok Assay"] = { slot = "BL", spellID = 0, item = "Ground Scorpok Assay", icon = "inv_misc_dust_02" },
|
||||
["Spirit of Boar"] = { slot = "BL", spellID = 10668, item = "Lung Juice Cocktail", icon = "inv_drink_12" },
|
||||
["Lung Juice Cocktail"] = { slot = "BL", spellID = 0, item = "Lung Juice Cocktail", icon = "inv_drink_12" },
|
||||
["Infallible Mind"] = { slot = "BL", spellID = 10692, item = "Cerebral Cortex Compound", icon = "inv_potion_32" }, -- same icon as the Mongoose elixir -- genuine, wowhead-confirmed
|
||||
["Cerebral Cortex Compound"] = { slot = "BL", spellID = 0, item = "Cerebral Cortex Compound", icon = "inv_potion_32" },
|
||||
["Spiritual Domination"] = { slot = "BL", spellID = 10693, item = "Gizzard Gum", icon = "inv_misc_food_30" },
|
||||
["Gizzard Gum"] = { slot = "BL", spellID = 0, item = "Gizzard Gum", icon = "inv_misc_food_30" },
|
||||
|
||||
-- ZANZA (free-stacker; only one Zanza-potion effect at a time; buff
|
||||
-- name = item name, wowhead-confirmed)
|
||||
["Spirit of Zanza"] = { slot = "ZANZA", spellID = 24382, item = "Spirit of Zanza", icon = "inv_potion_30" },
|
||||
|
||||
-- GAE (split out of the old SP column)
|
||||
["Greater Arcane Elixir"] = { slot = "GAE", spellID = 17539, item = "Greater Arcane Elixir", icon = "inv_potion_25" },
|
||||
["Arcane Elixir"] = { slot = "GAE", spellID = 11390, item = "Arcane Elixir", icon = "inv_potion_30" },
|
||||
|
||||
-- SCHOOL (split out of the old SP column; one school elixir at a time)
|
||||
["Elixir of Shadow Power"] = { slot = "SCHOOL", spellID = 11474, item = "Elixir of Shadow Power", icon = "inv_potion_46" },
|
||||
["Elixir of Frost Power"] = { slot = "SCHOOL", spellID = 21920, item = "Elixir of Frost Power", icon = "inv_potion_03" },
|
||||
["Greater Frost Power"] = { slot = "SCHOOL", spellID = 56544, item = "Elixir of Greater Frost Power", icon = "inv_potion_13" }, -- AURA name (TurtleWoW-added). NOT measured in-game: taken from a TurtleWoW spell database (56544 = "Greater Frost Power") plus the item name in pfQuest-turtle's item DB; the aura drops "Elixir of" the same way Giants/Agility above do
|
||||
["Elixir of Greater Firepower"] = { slot = "SCHOOL", spellID = 26276, item = "Elixir of Greater Firepower", icon = "inv_potion_60" },
|
||||
["Elixir of Firepower"] = { slot = "SCHOOL", spellID = 7844, item = "Elixir of Firepower", icon = "inv_potion_33" },
|
||||
|
||||
-- DREAMT (TurtleWoW custom; own slot -- simultaneously active with
|
||||
-- GAE, SCHOOL and SHARD in the caster set)
|
||||
["Dreamtonic"] = { slot = "DREAMT", spellID = 45489, item = "Dreamtonic", icon = "inv_potion_10" }, -- use-spell ID; the aura's own ID is unverified
|
||||
|
||||
-- SHARD (TurtleWoW custom; own slot)
|
||||
["Dreamshard Elixir"] = { slot = "SHARD", spellID = 45427, item = "Dreamshard Elixir", icon = "inv_potion_12" }, -- use-spell ID; the aura's own ID is unverified
|
||||
|
||||
-- MP5 (narrowed: Nightfin Soup is a FOOD buff and lives there now)
|
||||
["Mageblood Potion"] = { slot = "MP5", spellID = 24363, item = "Mageblood Potion", icon = "inv_potion_45" },
|
||||
["Mageblood"] = { slot = "MP5", spellID = 24363, item = "Mageblood Potion", icon = "inv_potion_45" }, -- TWoW buff-name variant (old Data.lua kept both)
|
||||
|
||||
-- ARM
|
||||
["Elixir of Superior Defense"] = { slot = "ARM", spellID = 11348, item = "Elixir of Superior Defense", icon = "inv_potion_66" },
|
||||
["Elixir of Greater Defense"] = { slot = "ARM", spellID = 11349, item = "Elixir of Greater Defense", icon = "inv_potion_65" },
|
||||
|
||||
-- HPELX (alcohol split out into ALC; Fortitude stacks with it)
|
||||
["Elixir of Fortitude"] = { slot = "HPELX", spellID = 3593, item = "Elixir of Fortitude", icon = "inv_potion_43" },
|
||||
["Health II"] = { slot = "HPELX", spellID = 3593, item = "Elixir of Fortitude", icon = "inv_potion_43" }, -- AURA name on this client (measured live: SpellInfo(3593) = "Health II")
|
||||
|
||||
-- ALC (alcohol buffs -- ONE column by design although different
|
||||
-- types stack; see the header compromise note). Buff names and spell
|
||||
-- ids wowhead/octowow-confirmed 2026-08-11; the "Blue Label" aura
|
||||
-- name differs from the Blue item name, both keys are kept.
|
||||
["Medivh's Merlot"] = { slot = "ALC", spellID = 57106, item = "Medivh's Merlot", icon = "inv_drink_waterskin_05" }, -- use-spell ID; the aura's own ID is unverified
|
||||
["Medivh's Merlot Blue"] = { slot = "ALC", spellID = 0, item = "Medivh's Merlot Blue", icon = "inv_drink_waterskin_01" },
|
||||
["Medivh's Merlot Blue Label"] = { slot = "ALC", spellID = 57107, item = "Medivh's Merlot Blue", icon = "inv_drink_waterskin_01" }, -- the AURA name (octowow: spell 57107 "Medivh's Merlot Blue Label")
|
||||
["Rumsey Rum Black Label"] = { slot = "ALC", spellID = 25804, item = "Rumsey Rum Black Label", icon = "inv_drink_04" },
|
||||
["Rumsey Rum"] = { slot = "ALC", spellID = 20875, item = "Rumsey Rum", icon = "inv_drink_03" },
|
||||
["Rumsey Rum Light"] = { slot = "ALC", spellID = 25037, item = "Rumsey Rum Light", icon = "inv_drink_08" },
|
||||
["Gordok Green Grog"] = { slot = "ALC", spellID = 22789, item = "Gordok Green Grog", icon = "inv_drink_03" }, -- same icon as Rumsey Rum -- genuine, wowhead-confirmed
|
||||
["Kreeg's Stout Beatdown"] = { slot = "ALC", spellID = 22790, item = "Kreeg's Stout Beatdown", icon = "inv_drink_05" }, -- +25 Spirit/-5 Int -- the healer set's alcohol, not a stamina drink
|
||||
|
||||
-- FR (protection-potion slot rules are an UNVERIFIED gap, see header)
|
||||
["Greater Fire Protection"] = { slot = "FR", spellID = 17543, item = "Greater Fire Protection Potion", icon = "inv_potion_24", unverified = true },
|
||||
["Fire Protection"] = { slot = "FR", spellID = 7233, item = "Fire Protection Potion", icon = "inv_potion_16", unverified = true },
|
||||
|
||||
-- FrR/NR/SR/HR/AR (same UNVERIFIED protection-potion gap as FR).
|
||||
-- Use-effect spell IDs + item icons wowhead-confirmed 2026-08-11.
|
||||
-- Tier coverage is asymmetric by design: no Lesser Arcane potion and
|
||||
-- no Greater Holy potion exist in vanilla 1.12. The aura on the
|
||||
-- client likely drops the "Greater" (wowhead names both tiers
|
||||
-- "<School> Protection"); the greater keys mirror the FR convention
|
||||
-- and the ID match carries them regardless of the live aura name.
|
||||
["Greater Frost Protection"] = { slot = "FrR", spellID = 17544, item = "Greater Frost Protection Potion", icon = "inv_potion_20", unverified = true },
|
||||
["Frost Protection"] = { slot = "FrR", spellID = 7239, item = "Frost Protection Potion", icon = "inv_potion_13", unverified = true },
|
||||
["Greater Nature Protection"] = { slot = "NR", spellID = 17546, item = "Greater Nature Protection Potion", icon = "inv_potion_22", unverified = true },
|
||||
["Nature Protection"] = { slot = "NR", spellID = 7254, item = "Nature Protection Potion", icon = "inv_potion_06", unverified = true }, -- live aura name carries a trailing space (SpellInfo(7254)); scan/aura.lua's trimmed retry covers it
|
||||
["Greater Shadow Protection"] = { slot = "SR", spellID = 17548, item = "Greater Shadow Protection Potion", icon = "inv_potion_23", unverified = true },
|
||||
["Shadow Protection"] = { slot = "SR", spellID = 7242, item = "Shadow Protection Potion", icon = "inv_potion_44", unverified = true }, -- NAME collides with the priest buff (data/classbuffs.lua SPROT, ids 976/10957/10958): ID-before-name keeps them apart; a name-only scan cannot tell the two, which SR's default-off expectation mitigates
|
||||
["Holy Protection"] = { slot = "HR", spellID = 7245, item = "Holy Protection Potion", icon = "inv_potion_09", unverified = true },
|
||||
["Greater Arcane Protection"] = { slot = "AR", spellID = 17549, item = "Greater Arcane Protection Potion", icon = "inv_potion_83", unverified = true },
|
||||
|
||||
-- WPN: intentionally NO aura entries - temp enchant is not a buff
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.ResolveItem(buffName, effectText) -> item, icon
|
||||
-- The PURE identification ladder (spec: "Identification ladder"):
|
||||
-- 1. unique buff name -> item/icon straight from the entry
|
||||
-- 2. generic buff name -> match effectText against the entry's
|
||||
-- variant discrim patterns (plain Lua 5.0 string.find, first match
|
||||
-- wins) -> that variant's item/icon
|
||||
-- 3. no match / no effect text -> nil, nil (the caller falls back to
|
||||
-- the slot-generic icon: "slot filled, item unknown")
|
||||
-- An entry-level discrim (no variants) gates rule 1 the same way: the
|
||||
-- item only resolves when the effect line matches. Offline-testable --
|
||||
-- no WoW API, no upvalues beyond the data table.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.ResolveItem(buffName, effectText)
|
||||
local def = DC.CONSUMABLES[buffName]
|
||||
if not def then
|
||||
return nil, nil
|
||||
end
|
||||
if def.variants then
|
||||
if type(effectText) == "string" then
|
||||
local n = table.getn(def.variants)
|
||||
for i = 1, n do
|
||||
local v = def.variants[i]
|
||||
if v.discrim and string.find(effectText, v.discrim) then
|
||||
return v.item, v.icon
|
||||
end
|
||||
end
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
if def.discrim then
|
||||
if type(effectText) == "string"
|
||||
and string.find(effectText, def.discrim) then
|
||||
return def.item, def.icon
|
||||
end
|
||||
return nil, nil
|
||||
end
|
||||
return def.item, def.icon
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- spellID -> slotId map, generated from DC.CONSUMABLES (spellID > 0).
|
||||
-- Collision rule: at classify time an ID match beats a
|
||||
-- name match. data/classbuffs.lua later MERGES its rank IDs into this same
|
||||
-- map (slot ids are unique across tabs, so one flat map is safe).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.SPELL_TO_SLOT = {}
|
||||
for name, def in pairs(DC.CONSUMABLES) do
|
||||
if def.spellID and def.spellID > 0 then
|
||||
DC.SPELL_TO_SLOT[def.spellID] = def.slot
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,388 @@
|
||||
-- DopingControl data/enchants.lua
|
||||
-- Equipment tab: 15 enchant slots + 2 item-tile slots (both trinkets), in
|
||||
-- paperdoll order, with 1.12 inventory slot IDs.
|
||||
-- Enchant columns are checked present/missing only (enchant ID in the item
|
||||
-- link ~= 0); an EMPTY enchant slot is always a gap (core/model.lua), so it
|
||||
-- is never gated by the expectation checkbox -- only the "has an enchant"
|
||||
-- question is. Item-tile slots (trinkets) show the equipped item and
|
||||
-- ignore expectations entirely; only an EMPTY slot counts as a gap there.
|
||||
--
|
||||
-- Server note: neck, both rings AND the waist ARE enchantable on this
|
||||
-- server (a server-custom feature beyond vanilla WoW, where none of them
|
||||
-- take an enchant) -- NECK/R1/R2/WAIST are therefore kind="ench" like any
|
||||
-- armor slot, not item tiles. The waist enchant is a "Belt Buckle" item
|
||||
-- (Copper/Bronze/Thorium/Dreamsteel/Bloody Belt Buckle),
|
||||
-- the same custom itemization line as the neck/ring gems. Their
|
||||
-- expectation defaults to OFF (data/expectations.lua): unlike the ARMOR
|
||||
-- enchant slots, a neck/ring/waist enchant is not assumed baseline raid
|
||||
-- gear, so the columns only track it once a role/class opts in via the
|
||||
-- options grid.
|
||||
--
|
||||
-- Bug history (2026-08-30): the waist (invSlot 6) was missing from this
|
||||
-- table entirely -- not a deliberate exclusion, an oversight that predates
|
||||
-- the Belt Buckle lookup above. Because scan/gear.lua's G.InvSlots()
|
||||
-- derives the full GetInventoryItemLink read list from THIS table, the
|
||||
-- omission silently dropped the waist from every equipment read: no raid
|
||||
-- dump ever carried a ";6=" entry, and the HIT tab under-counted any
|
||||
-- +hit/+resist on a belt (scan/hit.lua's H.SLOTS already listed 6 and
|
||||
-- expected scan/gear.lua's rawLinks to cover it -- see that file's
|
||||
-- ReadUnit comment). Adding WAIST here fixes both at the source.
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Slot definitions (array, order = column order). Same field shape as the
|
||||
-- other tabs plus invSlot (GetInventoryItemLink slot number). Ids
|
||||
-- HEAD/SHLD/... with tile labels HEA/SHO/...
|
||||
-- ------------------------------------------------------------------
|
||||
DC.SLOTS_EQUIPMENT = {
|
||||
{ id = "HEAD", label = "HEA", sub = "slot 1", full = "Head", kind = "ench", invSlot = 1 },
|
||||
{ id = "NECK", label = "NCK", sub = "slot 2", full = "Neck", kind = "ench", invSlot = 2 }, -- enchantable on this server (custom, unlike vanilla)
|
||||
{ id = "SHLD", label = "SHO", sub = "slot 3", full = "Shoulders", kind = "ench", invSlot = 3 },
|
||||
{ id = "BACK", label = "BCK", sub = "slot 15", full = "Back", kind = "ench", invSlot = 15 },
|
||||
{ id = "CHST", label = "CHE", sub = "slot 5", full = "Chest", kind = "ench", invSlot = 5 },
|
||||
{ id = "WAIST", label = "WAI", sub = "slot 6", full = "Waist", kind = "ench", invSlot = 6 }, -- enchantable on this server (custom Belt Buckle line, unlike vanilla)
|
||||
{ id = "WRST", label = "WRI", sub = "slot 9", full = "Wrist", kind = "ench", invSlot = 9 },
|
||||
{ id = "HAND", label = "HND", sub = "slot 10", full = "Hands", kind = "ench", invSlot = 10 },
|
||||
{ id = "LEGS", label = "LEG", sub = "slot 7", full = "Legs", kind = "ench", invSlot = 7 },
|
||||
{ id = "FEET", label = "FEE", sub = "slot 8", full = "Feet", kind = "ench", invSlot = 8 },
|
||||
{ id = "MAIN", label = "MH", sub = "slot 16", full = "Main hand", kind = "ench", invSlot = 16 },
|
||||
{ id = "OFFH", label = "OH", sub = "slot 17", full = "Off hand", kind = "ench", invSlot = 17 },
|
||||
{ id = "RNGD", label = "RNG", sub = "slot 18", full = "Ranged (scope)", kind = "ench", invSlot = 18 }, -- scope counts as the enchant
|
||||
{ id = "R1", label = "R1", sub = "slot 11", full = "Ring 1", kind = "ench", invSlot = 11 }, -- enchantable on this server (custom, unlike vanilla)
|
||||
{ id = "R2", label = "R2", sub = "slot 12", full = "Ring 2", kind = "ench", invSlot = 12 }, -- enchantable on this server (custom, unlike vanilla)
|
||||
{ id = "T1", label = "T1", sub = "slot 13", full = "Trinket 1", kind = "trinket", invSlot = 13 },
|
||||
{ id = "T2", label = "T2", sub = "slot 14", full = "Trinket 2", kind = "trinket", invSlot = 14 },
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Item-subtype exemptions: whether an enchant applies at all is a
|
||||
-- property of the equipped ITEM, not of the role. If the equipped item's
|
||||
-- itemSubType (GetItemInfo return 6 on TWoW 1.18.1)
|
||||
-- is listed for the slot, the cell classifies as NOTEXP instead
|
||||
-- of MISSING.
|
||||
--
|
||||
-- Shape: DC.ENCH_EXEMPT[slotId][itemSubType] = true
|
||||
--
|
||||
-- [ASSUMPTION] English-client subtype strings: wands = "Wands", off-hand
|
||||
-- frills/holdables = "Miscellaneous", thrown = "Thrown" (thrown weapons
|
||||
-- cannot take a scope -- same item-property principle as wands; "Thrown"
|
||||
-- is a deliberate extension --
|
||||
-- kept because dropping it would show thrown users a
|
||||
-- false MISSING scope). All three strings are on the probe checklist:
|
||||
-- the /dc probe battery dumps GetItemInfo subType (return 6) for every
|
||||
-- equipped slot ("Q5b" lines, core/probe.lua) -- a mismatch means the
|
||||
-- exemption silently never fires and must be corrected here.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.ENCH_EXEMPT = {
|
||||
RNGD = { ["Wands"] = true, ["Thrown"] = true }, -- Wands: no scope; Thrown: no scope [ASSUMPTION]
|
||||
OFFH = { ["Miscellaneous"] = true }, -- held-in-off-hand frills: not enchantable
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Class-based enchant exemptions:
|
||||
-- whole CLASSES for which an ench slot never applies,
|
||||
-- regardless of what (if anything) is equipped. DC_Model.classify checks
|
||||
-- this FIRST, before gearRead/EMPTY/expectation logic, and returns
|
||||
-- NOTEXP (detail "no ranged enchant for this class").
|
||||
--
|
||||
-- Shape: DC.ENCH_CLASS_EXEMPT[slotId][classToken] = true
|
||||
-- (classToken = english GetRaidRosterInfo return-6 token)
|
||||
--
|
||||
-- RNGD: shamans/druids/priests/mages/warlocks carry wands/totems/idols/
|
||||
-- relics or nothing there -- no scope ever applies.
|
||||
-- PALADIN added: vanilla paladins have a permanently EMPTY ranged
|
||||
-- slot (no ranged/relic item exists for them on 1.12), so a scope can
|
||||
-- never apply either.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.ENCH_CLASS_EXEMPT = {
|
||||
RNGD = {
|
||||
SHAMAN = true,
|
||||
DRUID = true,
|
||||
PRIEST = true,
|
||||
MAGE = true,
|
||||
WARLOCK = true,
|
||||
PALADIN = true, -- permanently empty ranged slot in vanilla
|
||||
},
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Enchant ID -> display text (1.12 has no API for it). DC.ENCH_NAMES below
|
||||
-- is generated from the client's own SpellItemEnchantment.dbc, restricted
|
||||
-- to permanent item enchants (ENCHANT_ITEM spell effects).
|
||||
-- DC.EnchantName(id) -> name | nil (unknown id, or no enchant: 0/nil).
|
||||
-- DC.EnchantLabel(id) -> name | "enchant #<id>" | nil (only for no enchant):
|
||||
-- an id missing from the table is still KNOWN to be enchanted, so it is
|
||||
-- never reported as missing -- only its name is unknown.
|
||||
-- ------------------------------------------------------------------
|
||||
function DC.EnchantName(id)
|
||||
if type(id) ~= "number" or id <= 0 or not DC.ENCH_NAMES then
|
||||
return nil
|
||||
end
|
||||
return DC.ENCH_NAMES[id]
|
||||
end
|
||||
|
||||
function DC.EnchantLabel(id)
|
||||
if type(id) ~= "number" or id <= 0 then
|
||||
return nil
|
||||
end
|
||||
return DC.EnchantName(id) or ("enchant #" .. id)
|
||||
end
|
||||
|
||||
-- BEGIN GENERATED ENCH_NAMES (tools/gen-enchant-names.py; do not edit by hand)
|
||||
DC.ENCH_NAMES = {
|
||||
[1] = "Rockbiter 3",
|
||||
[3] = "Flametongue 3",
|
||||
[15] = "Reinforced Armor +8/+1 Stamina",
|
||||
[16] = "Reinforced Armor +16/+2 Stamina",
|
||||
[17] = "Reinforced Armor +24/+3 Stamina",
|
||||
[18] = "Reinforced Armor +32/+4 Stamina",
|
||||
[24] = "Mana +5",
|
||||
[28] = "+4 All Resistances",
|
||||
[30] = "Scope (+1 Damage)",
|
||||
[32] = "Scope (+2 Damage)",
|
||||
[33] = "Scope (+3 Damage)",
|
||||
[34] = "Counterweight +3% Attack Speed",
|
||||
[36] = "Enchant: Fiery Blaze",
|
||||
[37] = "Weapon Chain - Immune Disarm",
|
||||
[41] = "Health +5",
|
||||
[43] = "Iron Spike (8-12)",
|
||||
[44] = "Absorption (10)",
|
||||
[63] = "Absorption (25)",
|
||||
[65] = "+1 All Resistances",
|
||||
[66] = "Stamina +1",
|
||||
[71] = "+1 Stamina",
|
||||
[72] = "+2 Stamina",
|
||||
[73] = "+3 Stamina",
|
||||
[76] = "+3 Agility",
|
||||
[80] = "+2 Intellect",
|
||||
[90] = "+4 Agility",
|
||||
[92] = "+6 Agility",
|
||||
[93] = "+7 Agility",
|
||||
[94] = "+4 Intellect",
|
||||
[96] = "+6 Intellect",
|
||||
[98] = "+4 Spirit",
|
||||
[104] = "+6 Stamina",
|
||||
[107] = "+5 Strength",
|
||||
[108] = "+6 Strength",
|
||||
[208] = "Healing +7",
|
||||
[225] = "Nature Damage +9",
|
||||
[241] = "Weapon Damage +2",
|
||||
[242] = "Health +15",
|
||||
[243] = "Spirit +1",
|
||||
[246] = "Mana +20",
|
||||
[247] = "Agility +1",
|
||||
[248] = "Strength +1",
|
||||
[249] = "Beastslaying +2",
|
||||
[250] = "Weapon Damage +1",
|
||||
[254] = "Health +25",
|
||||
[255] = "Spirit +3",
|
||||
[256] = "+5 Fire Resistance",
|
||||
[345] = "+40 Armor",
|
||||
[354] = "+9 Intellect",
|
||||
[357] = "+9 Strength",
|
||||
[362] = "+10 Strength",
|
||||
[370] = "+12 Spirit",
|
||||
[371] = "+12 Stamina",
|
||||
[426] = "Spell Damage +5",
|
||||
[432] = "Spell Damage +12",
|
||||
[440] = "Healing +12",
|
||||
[449] = "Rift Tear",
|
||||
[450] = "Scope (+2% Ranged Crit)",
|
||||
[463] = "Mithril Spike (16-20)",
|
||||
[464] = "Mithril Spurs",
|
||||
[663] = "Scope (+5 Damage)",
|
||||
[664] = "Scope (+7 Damage)",
|
||||
[684] = "+15 Strength",
|
||||
[723] = "Intellect +3",
|
||||
[724] = "Stamina +3",
|
||||
[744] = "Armor +20",
|
||||
[763] = "Block +1%",
|
||||
[783] = "Armor +10",
|
||||
[803] = "Fiery Weapon",
|
||||
[804] = "+10 Shadow Resistance",
|
||||
[805] = "Weapon Damage +4",
|
||||
[823] = "Strength +3",
|
||||
[843] = "Mana +30",
|
||||
[844] = "Mining +2",
|
||||
[845] = "Herbalism +2",
|
||||
[846] = "Fishing +2",
|
||||
[847] = "All Stats +1",
|
||||
[848] = "Armor +30",
|
||||
[849] = "Agility +3",
|
||||
[850] = "Health +35",
|
||||
[851] = "Spirit +5",
|
||||
[852] = "Stamina +5",
|
||||
[853] = "Beastslaying +6",
|
||||
[854] = "Elemental Slayer +6",
|
||||
[856] = "Strength +5",
|
||||
[857] = "Mana +50",
|
||||
[863] = "Blocking +2%",
|
||||
[865] = "Skinning +5",
|
||||
[866] = "All Stats +2",
|
||||
[883] = "+15 Agility",
|
||||
[884] = "Armor +50",
|
||||
[903] = "+3 All Resistances",
|
||||
[904] = "Agility +5",
|
||||
[905] = "Intellect +5",
|
||||
[906] = "Mining +5",
|
||||
[907] = "Spirit +7",
|
||||
[908] = "Health +50",
|
||||
[909] = "Herbalism +5",
|
||||
[910] = "Increased Stealth",
|
||||
[911] = "Minor Speed Increase",
|
||||
[912] = "Demonslaying",
|
||||
[913] = "Mana +65",
|
||||
[923] = "Defense +3",
|
||||
[924] = "Defense +1",
|
||||
[925] = "Defense +2",
|
||||
[926] = "+8 Frost Resistance",
|
||||
[927] = "Strength +7",
|
||||
[928] = "All Stats +3",
|
||||
[929] = "Stamina +7",
|
||||
[930] = "Minor Mount Speed Increase",
|
||||
[931] = "Attack Speed +1%",
|
||||
[943] = "Weapon Damage +3",
|
||||
[963] = "Weapon Damage +7",
|
||||
[1023] = "Feedback 1",
|
||||
[1068] = "+15 Stamina",
|
||||
[1125] = "+22 Intellect",
|
||||
[1149] = "+20 Spirit",
|
||||
[1250] = "+8 Arcane Resistance",
|
||||
[1252] = "+10 Arcane Resistance",
|
||||
[1257] = "+15 Arcane Resistance",
|
||||
[1267] = "+25 Arcane Resistance",
|
||||
[1341] = "+7 Fire Resistance",
|
||||
[1342] = "+8 Fire Resistance",
|
||||
[1344] = "+10 Fire Resistance",
|
||||
[1388] = "+8 Nature Resistance",
|
||||
[1434] = "+8 Shadow Resistance",
|
||||
[1436] = "+10 Shadow Resistance",
|
||||
[1441] = "+15 Shadow Resistance",
|
||||
[1446] = "+20 Shadow Resistance",
|
||||
[1483] = "Mana +150",
|
||||
[1503] = "HP +100",
|
||||
[1504] = "Armor +125",
|
||||
[1505] = "+20 Fire Resistance",
|
||||
[1506] = "Strength +8",
|
||||
[1507] = "Stamina +8",
|
||||
[1508] = "Agility +8",
|
||||
[1509] = "Intellect +8",
|
||||
[1510] = "Spirit +8",
|
||||
[1523] = "MANA/FR +85/14",
|
||||
[1524] = "HP/FR +75/14",
|
||||
[1525] = "AC/FR +110/14",
|
||||
[1526] = "STR/FR +10/14",
|
||||
[1527] = "STA/FR +10/14",
|
||||
[1528] = "AGI/FR +10/14",
|
||||
[1529] = "INT/FR +10/14",
|
||||
[1530] = "SPI/FR +10/14",
|
||||
[1532] = "STR/STA/AC/FR +10/10/110/15",
|
||||
[1543] = "INT/SPI/MANA/FR +10/10/100/15",
|
||||
[1704] = "Thorium Spike (20-30)",
|
||||
[1843] = "Reinforced Armor +40/+5 Stamina",
|
||||
[1883] = "Intellect +7",
|
||||
[1884] = "Spirit +9",
|
||||
[1885] = "Strength +9",
|
||||
[1886] = "Stamina +9",
|
||||
[1887] = "Agility +7",
|
||||
[1888] = "+5 All Resistances",
|
||||
[1889] = "Armor +70",
|
||||
[1890] = "Spirit +9",
|
||||
[1891] = "All Stats +4",
|
||||
[1892] = "Health +100",
|
||||
[1893] = "Mana +100",
|
||||
[1894] = "Icy Weapon",
|
||||
[1896] = "Weapon Damage +9",
|
||||
[1897] = "Weapon Damage +5",
|
||||
[1898] = "Lifestealing",
|
||||
[1899] = "Unholy Weapon",
|
||||
[1900] = "Crusader",
|
||||
[1903] = "Spirit +9",
|
||||
[1904] = "Intellect +9",
|
||||
[1944] = "+5 Defense",
|
||||
[2045] = "+14 Ranged Attack Power",
|
||||
[2082] = "+6 Arcane Spell Damage",
|
||||
[2084] = "+9 Arcane Spell Damage",
|
||||
[2144] = "+40 Shadow Spell Damage",
|
||||
[2158] = "+6 Fire Spell Damage",
|
||||
[2160] = "+9 Fire Spell Damage",
|
||||
[2236] = "+9 Frost Spell Damage",
|
||||
[2296] = "+40 Nature Spell Damage",
|
||||
[2366] = "+2 mana every 5 sec.",
|
||||
[2443] = "Frost Spell Damage +7",
|
||||
[2463] = "+7 Fire Resistance",
|
||||
[2483] = "+5 Fire Resistance",
|
||||
[2484] = "+5 Frost Resistance",
|
||||
[2485] = "+5 Arcane Resistance",
|
||||
[2486] = "+5 Nature Resistance",
|
||||
[2487] = "+5 Shadow Resistance",
|
||||
[2488] = "+5 All Resistances",
|
||||
[2503] = "Defense +5",
|
||||
[2504] = "Spell Damage +30",
|
||||
[2505] = "Healing Spells +55",
|
||||
[2523] = "Scope (+3% Ranged Hit)",
|
||||
[2544] = "Healing and Spell Damage +12",
|
||||
[2545] = "Dodge +1%",
|
||||
[2563] = "Strength +15",
|
||||
[2564] = "Agility +15",
|
||||
[2565] = "Mana Regen 4 per 5 sec.",
|
||||
[2566] = "Healing Spells +24",
|
||||
[2567] = "Spirit +20",
|
||||
[2568] = "Intellect +22",
|
||||
[2583] = "Defense +7/Stamina +10/Block Value +15",
|
||||
[2584] = "Defense +7/Stamina +10/Healing Spells +24",
|
||||
[2585] = "Attack Power +28/Dodge +1%",
|
||||
[2586] = "Ranged Attack Power +24/Stamina +10/Hit +1%",
|
||||
[2587] = "Healing and Spell Damage +13/Intellect +15",
|
||||
[2588] = "Healing and Spell Damage +18/Spell Hit +1%",
|
||||
[2589] = "Healing and Spell Damage +18/Stamina +10",
|
||||
[2590] = "Mana Regen +4/Stamina +10/Healing Spells +24",
|
||||
[2591] = "Intellect +10/Stamina +10/Healing Spells +24",
|
||||
[2603] = "Eternium Line",
|
||||
[2604] = "+33 Healing Spells",
|
||||
[2605] = "+18 Spell Damage and Healing",
|
||||
[2606] = "+30 Attack Power",
|
||||
[2613] = "Threat +2%",
|
||||
[2614] = "Shadow Damage +20",
|
||||
[2615] = "Frost Damage +20",
|
||||
[2616] = "Fire Damage +20",
|
||||
[2617] = "Healing Spells +30",
|
||||
[2619] = "+15 Fire Resistance",
|
||||
[2620] = "+15 Nature Resistance",
|
||||
[2621] = "Subtlety",
|
||||
[2622] = "Dodge +1%",
|
||||
[2646] = "Agility +25",
|
||||
[2681] = "+10 Nature Resistance",
|
||||
[2682] = "+10 Frost Resistance",
|
||||
[2683] = "+10 Shadow Resistance",
|
||||
[2715] = "Healing +31 and 5 mana per 5 sec.",
|
||||
[2716] = "Stamina +16 and Armor +100",
|
||||
[2717] = "Attack Power +26 and +1% Critical Strike",
|
||||
[2721] = "Spell Damage +15 and +1% Spell Critical Strike",
|
||||
[2722] = "Block Value +10",
|
||||
[3001] = "Arcane Damage +20",
|
||||
[3002] = "Nature Damage +20",
|
||||
[3003] = "Holy Damage +20",
|
||||
[3004] = "Wolfsheart",
|
||||
[3005] = "+14 Stamina",
|
||||
[3008] = "+6 Spell Penetration",
|
||||
[3011] = "+1% Vampirism",
|
||||
[3012] = "+2% Vampirism",
|
||||
[3016] = "+1% Haste",
|
||||
[3017] = "+2% Haste",
|
||||
[3021] = "+3 Spell Power",
|
||||
[3024] = "+5 Defense",
|
||||
[3025] = "+9 Damage and Healing Spells",
|
||||
[3026] = "Thorium Spurs",
|
||||
[3038] = "Armor Penetration +40",
|
||||
[3047] = "+25 Spell Penetration",
|
||||
[3049] = "+2% Dodge",
|
||||
[3051] = "+25 Armor Penetration",
|
||||
[3052] = "+10 Intellect",
|
||||
[3054] = "1% Resilience",
|
||||
[3057] = "Healing +8",
|
||||
}
|
||||
-- END GENERATED ENCH_NAMES
|
||||
@@ -0,0 +1,151 @@
|
||||
-- DopingControl data/expectations.lua
|
||||
-- Default expectation matrix DC.DEFAULT_EXPECT[role][slotId] = true.
|
||||
-- One flat slot-id namespace across all three tabs (ids are unique).
|
||||
--
|
||||
-- This table STAYS the role x slot
|
||||
-- SEED. The saved/live shape is per class --
|
||||
-- db.expectations[role][class][slotId] -- materialized from these lines
|
||||
-- by core/config.lua (DC.EnsureExpectations) for every class that can
|
||||
-- perform the role (DC_Roles.ROLE_CLASSES). Class-differentiated
|
||||
-- DEFAULTS are deliberately NOT shipped; the one
|
||||
-- motivating example lives here as a comment only:
|
||||
-- a TANK-confirmed SHAMAN wants AI = true (Arcane Intellect for the
|
||||
-- mana-fed tank kit) while a TANK WARRIOR does not -- the user flips
|
||||
-- expectations.TANK.SHAMAN.AI in the options grid. AI stays
|
||||
-- a CASTER/HEALER role default for every performing class.
|
||||
--
|
||||
-- Role-line choices:
|
||||
-- Consumables: the four role sets of the reference stacking rules
|
||||
-- (TurtleWoW 1.17.2 -- melee / caster / tank / healer, each "all
|
||||
-- active simultaneously") ARE the defaults now. RANGED
|
||||
-- has no reference set of its own and is derived from the melee
|
||||
-- line (no STR -- ranged AP scales off AGI -- and no
|
||||
-- FLASK). FR stays situational for all five roles
|
||||
-- (enable/disable per boss via the expectation matrix),
|
||||
-- WPN as before. The other school protection slots
|
||||
-- (FrR/NR/SR/HR/AR, added 2026-08-11) carry NO seed entry
|
||||
-- at all: default off for every role, so the dynamic-
|
||||
-- column rule hides them until someone enables them per
|
||||
-- role/class for a matching boss (same treatment as EMB
|
||||
-- and T1/T2 below) -- unlike FR, which ships seeded true.
|
||||
-- Class buffs: SOUL only HEALER, AI only CASTER/HEALER, SPROT situational
|
||||
-- for all five -- like FR. EMB (Emerald Blessing) has NO
|
||||
-- seed entry: the buff comes from a raid questline and most
|
||||
-- druids do not have it. The column therefore stays hidden
|
||||
-- until someone enables it per role/class in the options
|
||||
-- grid; the dynamic-column rule makes an unexpected slot
|
||||
-- disappear entirely (same treatment as T1/T2 below).
|
||||
-- Equipment: ALL armor enchant slots for ALL roles, WAIST included
|
||||
-- (owner decision, 2026-08-30: every role wears a belt,
|
||||
-- and it is enchantable on this server via the Belt
|
||||
-- Buckle item line -- so it is worn equipment like any
|
||||
-- other slot, not an optional extra like neck/rings);
|
||||
-- per-item exemptions (wand/holdable) are handled at
|
||||
-- classify time via DC.ENCH_EXEMPT, not here.
|
||||
-- NECK/R1/R2 (also enchantable on this server, a custom
|
||||
-- feature beyond vanilla) stay OFF by default for every
|
||||
-- role: unlike WAIST, a neck/ring enchant is not assumed
|
||||
-- baseline raid gear -- enable it per role/class in the
|
||||
-- options grid when it matters. T1/T2 (trinket item tiles)
|
||||
-- carry NO seed entry at all -- the model ignores
|
||||
-- expectations for tile-kind slots entirely (item
|
||||
-- present/absent decides, always shown, never gated).
|
||||
--
|
||||
-- Known debatable rows -- documented deliberately, the shipped defaults
|
||||
-- win:
|
||||
-- * TANK+AP / MELEE+FLASK: the OLD defaults deliberately excluded them;
|
||||
-- the reference role sets include them (tank runs Winterfall
|
||||
-- Firewater, melee runs Flask of the Titans), so both
|
||||
-- flipped to true with the v6 slot model.
|
||||
-- * RANGED line: derived, not covered by the reference sets (see above) -- BL is included
|
||||
-- (Ground Scorpok Assay is +25 AGI), STR/FLASK are not.
|
||||
-- * HEALER+HPELX: the reference healer set carries no Elixir of Fortitude, so
|
||||
-- HPELX defaults off for HEALER while ALC (Kreeg's
|
||||
-- Stout Beatdown + Medivh's Merlot Blue) is on.
|
||||
-- * ZANZA: expected for every role (all four reference sets carry it).
|
||||
-- * Equipment: an alternative default would differentiate per role
|
||||
-- (OFFH only TANK/MELEE, RNGD only RANGED,
|
||||
-- casters/healers neither); deliberately flattened to
|
||||
-- all-roles + ENCH_EXEMPT item checks.
|
||||
--
|
||||
-- This table is the pristine seed: core/config.lua materializes it into
|
||||
-- db.expectations (per class, deep copies) on first load; the options
|
||||
-- editor mutates only the materialized copies, never this table.
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
DC.DEFAULT_EXPECT = {
|
||||
TANK = {
|
||||
-- consumables (reference tank set: Mongoose, Firewater, Juju Power,
|
||||
-- R.O.I.D.S., Zanza, Superior Defense, Fortitude, Titans, food,
|
||||
-- Merlot + Rumsey, sharpening stone; Gift of Arthas has no column)
|
||||
FLASK = true, FOOD = true, AP = true, STR = true, AGI = true,
|
||||
BL = true, ZANZA = true, ARM = true, HPELX = true, ALC = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment (RNGD default only for RANGED:
|
||||
-- scopes only matter for hunters; flip in the options
|
||||
-- expectation matrix anytime)
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
MELEE = {
|
||||
-- consumables (reference melee set: Mongoose, Firewater/Juju Might,
|
||||
-- Juju Power/Giants, R.O.I.D.S./Scorpok, Zanza, Titans, food,
|
||||
-- sharpening stone; Juju Flurry has no column)
|
||||
FLASK = true, FOOD = true, AP = true, STR = true, AGI = true,
|
||||
BL = true, ZANZA = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
RANGED = {
|
||||
-- consumables (derived from the melee line -- no reference set; no STR,
|
||||
-- no FLASK, see "Known debatable rows")
|
||||
FOOD = true, AP = true, AGI = true, BL = true, ZANZA = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
RNGD = true,
|
||||
},
|
||||
-- CASTER/HEALER: RNGD also dropped by role default (their classes are
|
||||
-- additionally exempt via ENCH_CLASS_EXEMPT - belt and suspenders)
|
||||
CASTER = {
|
||||
-- consumables (reference caster set: Dreamshard, GAE, school elixir,
|
||||
-- Dreamtonic, Mageblood, wizard oil, Zanza, Supreme Power, food)
|
||||
FLASK = true, FOOD = true, GAE = true, SCHOOL = true,
|
||||
DREAMT = true, SHARD = true, MP5 = true, ZANZA = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs
|
||||
AI = true, MOTW = true, PWF = true, SPROT = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
HEALER = {
|
||||
-- consumables (reference healer set: Dreamshard, Mageblood, mana oil,
|
||||
-- Zanza, Distilled Wisdom, food, Kreeg's + Merlot Blue)
|
||||
FLASK = true, FOOD = true, MP5 = true, SHARD = true, ZANZA = true,
|
||||
ALC = true,
|
||||
FR = true, WPN = true,
|
||||
-- class buffs (SOUL: expected ONLY here)
|
||||
AI = true, MOTW = true, PWF = true, SPROT = true, SOUL = true,
|
||||
-- equipment
|
||||
HEAD = true, SHLD = true, BACK = true, CHST = true, WRST = true,
|
||||
HAND = true, LEGS = true, FEET = true, MAIN = true, OFFH = true,
|
||||
WAIST = true,
|
||||
},
|
||||
}
|
||||
+319
@@ -0,0 +1,319 @@
|
||||
-- DopingControl data/hit.lua
|
||||
-- Hit-chance tables for the HIT tab: what GEAR and AURAS contribute, plus
|
||||
-- the caps every column is judged against. The third contributor,
|
||||
-- TALENTS, lives in its own file (data/talenthit.lua) because it is a
|
||||
-- different kind of source -- looked up by talent name and rank rather
|
||||
-- than parsed out of a tooltip.
|
||||
--
|
||||
-- WHY TOOLTIP PARSING (measured, not assumed): this client family exposes
|
||||
-- NO hit API at all -- GetHitModifier, GetSpellHitModifier and
|
||||
-- GetCombatRating are all absent (verified on the live client). The only
|
||||
-- readable source of a player's +hit is the text of the equipped items'
|
||||
-- tooltips, which IS readable for foreign units in range via
|
||||
-- GameTooltip:SetInventoryItem(unit, slot). Everything in this file exists
|
||||
-- to turn those tooltip lines into a number and to bound that number
|
||||
-- against the caps of the current patch.
|
||||
--
|
||||
-- WHAT CANNOT BE READ (in principle, not a missing feature): a foreign
|
||||
-- player's WEAPON SKILL. The server never sends PLAYER_SKILL_INFO for
|
||||
-- anyone but the own character and no inspect-skill channel exists, so
|
||||
-- weapon skill is MEASURED for the own character (GetSkillLineInfo, see
|
||||
-- scan/hit.lua) and ESTIMATED for everyone else -- base skill + racial
|
||||
-- specialization + an optional per-player "weapon skill book" flag the
|
||||
-- user sets in the options. Every consumer must present the foreign value
|
||||
-- as an assumption, never as a measurement.
|
||||
--
|
||||
-- PATCH BASIS (current server patch): weapon-skill scaling was rewritten
|
||||
-- to LINEAR, the old 305-skill breakpoint was removed and the hidden 1%
|
||||
-- suppression was dropped. Consequences encoded below: the yellow melee
|
||||
-- hit cap against a boss three levels above the player is 8% (it was 9%
|
||||
-- before the rewrite) and the dual-wield WHITE cap is 27%. The spell hit
|
||||
-- cap is 16%, derived from the server's own clamp (see DC.HIT_CAPS below)
|
||||
-- rather than measured -- it is a single constant here so a later
|
||||
-- correction changes one line.
|
||||
--
|
||||
-- English client assumed: both the tooltip patterns and the weapon-skill
|
||||
-- names below are English strings. On a localized client nothing matches
|
||||
-- and the hit columns simply read 0 rather than showing wrong numbers.
|
||||
--
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Magic schools that can carry a school-specific spell-hit line.
|
||||
-- Order is display order for tooltip detail lines.
|
||||
--
|
||||
-- The CAPITALIZED spelling is load-bearing twice over: it is the word that
|
||||
-- appears in the item tooltip ("Improves your chance to hit with Fire
|
||||
-- spells by 1%"), so it is both the generated pattern text below and the
|
||||
-- key of the parsed `schools` table.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.HIT_SCHOOLS = { "Arcane", "Fire", "Frost", "Holy", "Nature", "Shadow" }
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.HIT_SCHOOL_KEY -- lowercase school id -> the CAPITALIZED key used by
|
||||
-- everything that came out of a tooltip.
|
||||
--
|
||||
-- Two spellings exist because two sources exist and neither may be bent to
|
||||
-- the other: tooltip text dictates "Fire", while the column definitions
|
||||
-- (DC.SLOTS_HIT, core/const.lua) and the talent tables
|
||||
-- (DC.TALENT_HIT, data/talenthit.lua) are hand-written data that follows
|
||||
-- the file-wide lowercase-id convention. This table is GENERATED from
|
||||
-- DC.HIT_SCHOOLS so the two can never drift apart, and DC_Hit.SchoolHit
|
||||
-- (scan/hit.lua) is the one function callers should use instead of
|
||||
-- indexing either table by hand.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.HIT_SCHOOL_KEY = {}
|
||||
for i = 1, table.getn(DC.HIT_SCHOOLS) do
|
||||
local school = DC.HIT_SCHOOLS[i]
|
||||
DC.HIT_SCHOOL_KEY[string.lower(school)] = school
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.HIT_PATTERNS -- ARRAY (order matters, FIRST MATCH WINS), each entry
|
||||
-- { pattern = <Lua 5.0 string.find pattern with ONE numeric capture>,
|
||||
-- kind = "melee" | "spell" | "both" | <school name> }
|
||||
--
|
||||
-- kind semantics (folded by DC_Hit.SumGear):
|
||||
-- "melee" generic physical hit. 1.12 has no separate ranged-hit affix:
|
||||
-- the same "chance to hit" line applies to melee AND ranged
|
||||
-- attacks, so the MELEE bucket feeds both hit columns.
|
||||
-- "spell" spell hit against every school.
|
||||
-- "both" "spells and attacks": adds to melee AND spell.
|
||||
-- <school> school-specific spell hit (e.g. "Fire"): it does NOT raise
|
||||
-- the general spell-hit number, it is carried separately and
|
||||
-- shown as an extra, because it only helps that one school.
|
||||
--
|
||||
-- Ordering rule: the more specific phrasings come first. The four
|
||||
-- phrasings do not actually overlap as literals ("hit with spells and
|
||||
-- attacks by", "hit with Fire spells by", "hit with spells by", "hit by"),
|
||||
-- but the order keeps that from becoming load-bearing if a pattern is ever
|
||||
-- widened.
|
||||
--
|
||||
-- Extension point: a phrasing that is not listed here is SILENTLY IGNORED
|
||||
-- (never guessed at). Server-custom item text that grants hit therefore
|
||||
-- needs its pattern added here -- this table is the single place.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.HIT_PATTERNS = {
|
||||
-- combined affix ("spells and attacks")
|
||||
{ pattern = "Improves your chance to hit with spells and attacks by (%d+)%%",
|
||||
kind = "both" },
|
||||
}
|
||||
|
||||
-- school-specific spell hit, generated so the school list stays the single
|
||||
-- source of truth ("Improves your chance to hit with Fire spells by 1%")
|
||||
for i = 1, table.getn(DC.HIT_SCHOOLS) do
|
||||
local school = DC.HIT_SCHOOLS[i]
|
||||
table.insert(DC.HIT_PATTERNS, {
|
||||
pattern = "Improves your chance to hit with " .. school
|
||||
.. " spells by (%d+)%%",
|
||||
kind = school,
|
||||
})
|
||||
end
|
||||
|
||||
-- general spell hit, then the generic (melee + ranged) hit line
|
||||
table.insert(DC.HIT_PATTERNS,
|
||||
{ pattern = "Improves your chance to hit with spells by (%d+)%%",
|
||||
kind = "spell" })
|
||||
table.insert(DC.HIT_PATTERNS,
|
||||
{ pattern = "Improves your chance to hit by (%d+)%%",
|
||||
kind = "melee" })
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.WEAPON_SKILL_OF_SUBTYPE -- GetItemInfo itemSubType -> weapon SKILL
|
||||
-- name (the string GetSkillLineInfo reports for that weapon).
|
||||
--
|
||||
-- Absent key = "not a weapon" and that is load-bearing: off-hand items
|
||||
-- whose subtype is missing here (shields, held-in-off-hand items, totems,
|
||||
-- idols, librams, fishing poles) are what tells the caller that the player
|
||||
-- is NOT dual-wielding.
|
||||
--
|
||||
-- Verified live on this client: "One-Handed Maces" (plus the non-weapon
|
||||
-- subtypes "Shields" and "Totems", deliberately absent). The remaining
|
||||
-- strings follow the standard 1.12 weapon subclass naming and are
|
||||
-- UNVERIFIED -- each one is only a lookup key, so a wrong string degrades
|
||||
-- to "unknown weapon" (no skill assumption, no dual-wield claim), never to
|
||||
-- a wrong number.
|
||||
--
|
||||
-- Two mappings are worth calling out:
|
||||
-- "Fist Weapons" -> "Unarmed": fist weapons run on the Unarmed skill
|
||||
-- line, they have no skill of their own.
|
||||
-- "Wands" -> "Wands": the skill line exists, but whether wand attacks
|
||||
-- obey weapon skill or spell hit is DISPUTED on this patch. The
|
||||
-- mapping only names the skill line; the ranged hit column
|
||||
-- deliberately ignores wands (see DC.RANGED_ATTACK_SUBTYPE).
|
||||
-- ------------------------------------------------------------------
|
||||
DC.WEAPON_SKILL_OF_SUBTYPE = {
|
||||
["One-Handed Axes"] = "Axes",
|
||||
["Two-Handed Axes"] = "Two-Handed Axes",
|
||||
["One-Handed Maces"] = "Maces", -- verified live
|
||||
["Two-Handed Maces"] = "Two-Handed Maces",
|
||||
["One-Handed Swords"] = "Swords",
|
||||
["Two-Handed Swords"] = "Two-Handed Swords",
|
||||
["Daggers"] = "Daggers",
|
||||
["Fist Weapons"] = "Unarmed", -- see header
|
||||
["Polearms"] = "Polearms",
|
||||
["Staves"] = "Staves",
|
||||
["Bows"] = "Bows",
|
||||
["Crossbows"] = "Crossbows",
|
||||
["Guns"] = "Guns",
|
||||
["Thrown"] = "Thrown",
|
||||
["Wands"] = "Wands", -- see header
|
||||
}
|
||||
|
||||
-- Subtypes that make the RANGED hit question meaningful, i.e. things that
|
||||
-- actually swing as a ranged ATTACK. Wands are excluded on purpose (their
|
||||
-- hit mechanic is disputed, see above) and so is every non-weapon relic;
|
||||
-- a player without one of these gets no ranged hit column value at all.
|
||||
DC.RANGED_ATTACK_SUBTYPE = {
|
||||
["Bows"] = true,
|
||||
["Crossbows"] = true,
|
||||
["Guns"] = true,
|
||||
["Thrown"] = true,
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.CLASS_WEAPON_SKILLS -- class token -> ordered array of the weapon
|
||||
-- skills that class can train (Vanilla 1.12 base list). Every name here is
|
||||
-- a VALUE of DC.WEAPON_SKILL_OF_SUBTYPE above, same spelling -- the
|
||||
-- options editor's Skill books tab (ui/options.lua, O.SkillBookRows)
|
||||
-- writes db.skillBooks[player][skill] keyed by these exact strings, so a
|
||||
-- typo here would silently create a book entry the hit calc never reads.
|
||||
--
|
||||
-- This server can diverge from the Vanilla baseline (extra or missing
|
||||
-- trainable weapons for a class). If a player is seen wielding a skill
|
||||
-- that is not in their class's list here, the Skill books tab still
|
||||
-- offers it -- a WORN skill always rides along regardless of the class
|
||||
-- list (see O.SkillBookRows) -- but if that turns out to be the NORMAL
|
||||
-- case for a class rather than a one-off exception, extend the list below
|
||||
-- rather than leaning on the worn-skill fallback forever.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.CLASS_WEAPON_SKILLS = {
|
||||
WARRIOR = { "Axes", "Two-Handed Axes", "Maces", "Two-Handed Maces",
|
||||
"Swords", "Two-Handed Swords", "Daggers", "Unarmed",
|
||||
"Polearms", "Staves", "Bows", "Crossbows", "Guns",
|
||||
"Thrown" },
|
||||
PALADIN = { "Axes", "Two-Handed Axes", "Maces", "Two-Handed Maces",
|
||||
"Swords", "Two-Handed Swords", "Polearms" },
|
||||
HUNTER = { "Axes", "Two-Handed Axes", "Swords", "Two-Handed Swords",
|
||||
"Daggers", "Unarmed", "Polearms", "Staves", "Bows",
|
||||
"Crossbows", "Guns", "Thrown" },
|
||||
ROGUE = { "Daggers", "Unarmed", "Maces", "Swords", "Bows",
|
||||
"Crossbows", "Guns", "Thrown" },
|
||||
PRIEST = { "Daggers", "Maces", "Staves", "Wands" },
|
||||
SHAMAN = { "Axes", "Two-Handed Axes", "Maces", "Two-Handed Maces",
|
||||
"Daggers", "Unarmed", "Staves" },
|
||||
MAGE = { "Daggers", "Swords", "Staves", "Wands" },
|
||||
WARLOCK = { "Daggers", "Swords", "Staves", "Wands" },
|
||||
DRUID = { "Maces", "Two-Handed Maces", "Daggers", "Unarmed",
|
||||
"Staves", "Polearms" },
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- Weapon skill baseline and modifiers.
|
||||
--
|
||||
-- DC.BASE_WEAPON_SKILL: a level-60 character caps every weapon skill at
|
||||
-- 300 (5 x level). This tool is a raid tool, so 60 is assumed for the
|
||||
-- estimate of FOREIGN players; the own character is measured and never
|
||||
-- uses this number.
|
||||
--
|
||||
-- DC.RACIAL_SKILL_BONUS: the racial weapon specialization bonus. The value
|
||||
-- is DISPUTED -- the classic value is +5, and a server-side change to +3
|
||||
-- has been reported but is UNVERIFIED. It is ONE constant on purpose:
|
||||
-- changing this single line changes every racial bonus and every derived
|
||||
-- cap.
|
||||
--
|
||||
-- DC.SKILL_BOOK_BONUS: a quest weapon-skill book grants +3 skill. It is
|
||||
-- invisible on other players (see the header), which is why it is an
|
||||
-- opt-in per-player checkbox in the options rather than a scan result.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.BASE_WEAPON_SKILL = 300
|
||||
DC.RACIAL_SKILL_BONUS = 5
|
||||
DC.SKILL_BOOK_BONUS = 3
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.RACE_WEAPON_SKILL -- race -> { [weapon skill name] = bonus }.
|
||||
--
|
||||
-- Keys: BOTH spellings are listed for every race that has a bonus -- the
|
||||
-- display name ("Night Elf") and the file token ("NightElf", "Scourge"),
|
||||
-- because the race string reaches us from UnitRace, whose two returns are
|
||||
-- exactly those two spellings. Aliases point at the SAME table, so a race
|
||||
-- is never counted twice.
|
||||
--
|
||||
-- Races without a weapon specialization (night elves, tauren, undead) are
|
||||
-- deliberately absent: an absent race resolves to "no bonus", which is
|
||||
-- also what an unknown/newer race token resolves to (nil-safe by design).
|
||||
-- ------------------------------------------------------------------
|
||||
local B = DC.RACIAL_SKILL_BONUS
|
||||
|
||||
local HUMAN_SKILLS = {
|
||||
["Swords"] = B, ["Two-Handed Swords"] = B,
|
||||
["Maces"] = B, ["Two-Handed Maces"] = B,
|
||||
}
|
||||
local ORC_SKILLS = {
|
||||
["Axes"] = B, ["Two-Handed Axes"] = B,
|
||||
}
|
||||
local DWARF_SKILLS = { ["Guns"] = B }
|
||||
local TROLL_SKILLS = { ["Bows"] = B, ["Thrown"] = B }
|
||||
local GNOME_SKILLS = { ["Daggers"] = B, ["Swords"] = B }
|
||||
-- races without a weapon specialization share ONE empty table
|
||||
local NO_SKILLS = {}
|
||||
|
||||
DC.RACE_WEAPON_SKILL = {
|
||||
["Human"] = HUMAN_SKILLS,
|
||||
["Orc"] = ORC_SKILLS,
|
||||
["Dwarf"] = DWARF_SKILLS,
|
||||
["Troll"] = TROLL_SKILLS,
|
||||
["Gnome"] = GNOME_SKILLS,
|
||||
-- no weapon specialization; listed for readability, empty on purpose
|
||||
["Night Elf"] = NO_SKILLS,
|
||||
["NightElf"] = NO_SKILLS,
|
||||
["Tauren"] = NO_SKILLS,
|
||||
["Undead"] = NO_SKILLS,
|
||||
["Scourge"] = NO_SKILLS,
|
||||
}
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.HIT_CAPS -- hit percentages needed against a boss THREE LEVELS above
|
||||
-- the player on the current patch, at base weapon skill (300):
|
||||
-- meleeYellow 8 -- special attacks / abilities ("yellow" swings)
|
||||
-- meleeWhiteDW 27 -- auto attacks while DUAL WIELDING (the 19% dual-
|
||||
-- wield penalty rides on top); a single weapon's white
|
||||
-- swings share the yellow cap
|
||||
-- ranged 8 -- ranged attacks behave like yellow melee here
|
||||
-- spell 16 -- the server clamps spell hit chance to 99%, so 1%
|
||||
-- miss can never be removed: base miss vs a +3-level
|
||||
-- boss is 17%, which makes 16% the effective cap
|
||||
--
|
||||
-- DC.CAP_PER_SKILL: every weapon skill point ABOVE 300 lowers the melee/
|
||||
-- ranged cap by 0.2 percentage points (linear scaling, see header). A
|
||||
-- racial specialization (+5 skill) therefore lowers the yellow cap from
|
||||
-- 8.0 to 7.0, and a weapon skill book (+3) is worth about 0.6% hit.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.HIT_CAPS = {
|
||||
meleeYellow = 8,
|
||||
meleeWhiteDW = 27,
|
||||
ranged = 8,
|
||||
spell = 16,
|
||||
}
|
||||
|
||||
DC.CAP_PER_SKILL = 0.2
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.HIT_AURAS -- hit granted by AURAS rather than gear, folded on top of
|
||||
-- the gear sum by scan/engine.lua's BuildPlayer (no extra scan: the buff
|
||||
-- names are already in the store from the same aura read the class-buff
|
||||
-- tab uses). Keys are buff NAMES (matched against player.auras.names);
|
||||
-- values are { melee=, ranged=, spell= } percentages -- every field is
|
||||
-- OPTIONAL, since a hit-granting aura need not touch all three columns.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.HIT_AURAS = {
|
||||
-- raid-wide druid buff: +1% SPELL hit only (also grants movement
|
||||
-- speed and casting mana regen, which do not concern this addon) --
|
||||
-- verified against the in-game tooltip text, so this is deliberately
|
||||
-- NOT a melee/ranged entry; add further hit-granting auras here (a
|
||||
-- food buff with hit would be the likeliest addition)
|
||||
["Emerald Blessing"] = { spell = 1 },
|
||||
}
|
||||
+139
@@ -0,0 +1,139 @@
|
||||
-- DopingControl data/resist.lua
|
||||
-- Resistance-reconstruction tables for the RESIST tab: what GEAR tooltips
|
||||
-- and RACE grant, mirroring data/hit.lua's split for the HIT tab (patterns
|
||||
-- + constants here, the pure parser in scan/resist.lua).
|
||||
--
|
||||
-- WHY RECONSTRUCTION (measured, not assumed -- see scan/hit.lua's header
|
||||
-- for the full story): the server does not transmit
|
||||
-- UNIT_FIELD_RESISTANCES for foreign players at all (Player::
|
||||
-- InitVisibleBits() never sets the bit for anyone but the owning client),
|
||||
-- so UnitResistance/GetUnitField can NEVER answer for a foreign unit --
|
||||
-- this is not a corner case scan/engine.lua's three read paths occasionally
|
||||
-- miss, it is the only path that can ever ANSWER for one. The equipped
|
||||
-- items' tooltips are readable for foreign units in range
|
||||
-- (GameTooltip:SetInventoryItem), exactly like the HIT tab's +hit lines,
|
||||
-- so the total is rebuilt from the same source: item stat text, plus the
|
||||
-- racial bonus below (which no tooltip ever states).
|
||||
--
|
||||
-- DC.RESIST_SCHOOLS -- the five schools that carry a resistance stat, in
|
||||
-- TOOLTIP capitalization ("Fire Resistance"), paired with the
|
||||
-- UnitResistance() school index used throughout the addon (core/const.lua
|
||||
-- DC.SLOTS_RESIST: 2 fire / 3 nature / 4 frost / 5 shadow / 6 arcane; 0
|
||||
-- physical and 1 holy are never queried -- there is no "Holy Resistance"
|
||||
-- item stat in 1.12, which is also why this list has no sixth entry).
|
||||
-- Kept in sync with DC.SLOTS_RESIST BY HAND (self-contained, like
|
||||
-- DC.HIT_SCHOOLS/DC.SLOTS_HIT in data/hit.lua and core/const.lua) --
|
||||
-- test_resist.lua pins the two against each other so they cannot drift
|
||||
-- apart silently.
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
DC.RESIST_SCHOOLS = {
|
||||
{ name = "Fire", school = 2 },
|
||||
{ name = "Nature", school = 3 },
|
||||
{ name = "Frost", school = 4 },
|
||||
{ name = "Shadow", school = 5 },
|
||||
{ name = "Arcane", school = 6 },
|
||||
}
|
||||
|
||||
-- DC.RESIST_SCHOOL_NUM -- tooltip school name -> UnitResistance() index,
|
||||
-- GENERATED from DC.RESIST_SCHOOLS so the two tables cannot drift apart
|
||||
-- (same idea as DC.HIT_SCHOOL_KEY in data/hit.lua).
|
||||
DC.RESIST_SCHOOL_NUM = {}
|
||||
for i = 1, table.getn(DC.RESIST_SCHOOLS) do
|
||||
local e = DC.RESIST_SCHOOLS[i]
|
||||
DC.RESIST_SCHOOL_NUM[e.name] = e.school
|
||||
end
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.RESIST_PATTERNS -- ARRAY (order matters, FIRST MATCH WINS), each
|
||||
-- entry { pattern = <Lua 5.0 string.find pattern with ONE numeric
|
||||
-- capture>, kind = <school name> | "all" }.
|
||||
--
|
||||
-- Measured tooltip forms (2026-08-02, own-character tooltip, English
|
||||
-- client):
|
||||
-- "+10 Arcane Resistance" -- PLAIN stat line, no "Equip:"
|
||||
-- "+10 Fire Resistance" -- (item base stats print this way,
|
||||
-- "+10 Nature Resistance" -- unlike +hit which is always an
|
||||
-- "+1 Fire Resistance" -- "Equip:" trigger line)
|
||||
-- "Equip: +8 All Resistances." -- multi-school EQUIP trigger --
|
||||
-- applies to ALL FIVE schools at
|
||||
-- once; a parser keyed on the five
|
||||
-- single-school patterns alone would
|
||||
-- silently miss this line entirely
|
||||
-- (the trap this table exists to
|
||||
-- close, see scan/resist.lua
|
||||
-- R.SumLines).
|
||||
--
|
||||
-- Both anchored at the LINE START (^): a stat line is the line's entire
|
||||
-- content on this client, so anchoring guards against a flavor-text
|
||||
-- sentence that happens to contain "Fire Resistance" mid-phrase. No
|
||||
-- trailing anchor: the plain form has nothing after the number+word (WoW
|
||||
-- classic tooltip text is fixed, so this cannot mismatch), and the Equip
|
||||
-- form is allowed to keep trailing punctuation.
|
||||
--
|
||||
-- Extension point (mirrors DC.HIT_PATTERNS): a phrasing not listed here is
|
||||
-- SILENTLY IGNORED, never guessed at. An "Equip: +N <School> Resistance."
|
||||
-- single-school equip form is conceivable on this server but UNMEASURED --
|
||||
-- it is deliberately absent until someone reads it off a real tooltip.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.RESIST_PATTERNS = {}
|
||||
for i = 1, table.getn(DC.RESIST_SCHOOLS) do
|
||||
local e = DC.RESIST_SCHOOLS[i]
|
||||
table.insert(DC.RESIST_PATTERNS, {
|
||||
pattern = "^%+(%d+) " .. e.name .. " Resistance",
|
||||
kind = e.name,
|
||||
})
|
||||
end
|
||||
table.insert(DC.RESIST_PATTERNS, {
|
||||
pattern = "^Equip: %+(%d+) All Resistances",
|
||||
kind = "all",
|
||||
})
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.RACE_RESIST -- race -> { [UnitResistance school index] = bonus }.
|
||||
-- INNATE racial resistance, granted by the race itself rather than any
|
||||
-- item -- invisible to a tooltip scan, so it has to be added on top of the
|
||||
-- gear sum by hand (scan/engine.lua's E.AssembleResists gear-fallback
|
||||
-- path) or the reconstructed total would silently understate every member
|
||||
-- of these five races by exactly this amount.
|
||||
--
|
||||
-- Keys: BOTH spellings for every race that has a bonus (display name and
|
||||
-- file token), same reasoning as DC.RACE_WEAPON_SKILL in data/hit.lua --
|
||||
-- scan/hit.lua's H.ReadUnit stores `token or display` as the race passed
|
||||
-- through the store, so either spelling must resolve. Aliases point at the
|
||||
-- SAME table (never a separate copy), so a race can never end up counted
|
||||
-- with two different bonus tables that could drift apart.
|
||||
--
|
||||
-- Verified racial resistances (task brief, 2026-08-02):
|
||||
-- Dwarf +10 Frost
|
||||
-- Gnome +10 Arcane
|
||||
-- Night Elf +10 Nature
|
||||
-- Tauren +10 Nature
|
||||
-- Undead/Forsaken +10 Shadow
|
||||
-- Human, Orc, Troll: none
|
||||
--
|
||||
-- WARNING -- NOT COVERED: TurtleWoW's two extra playable races, Goblin and
|
||||
-- High Elf/Blood Elf. Their racial resistance (if they even have one) is
|
||||
-- UNKNOWN -- it is not guessed here. Both are simply ABSENT from this
|
||||
-- table, which is safe by construction: DC_Resist.RaceBonus (scan/
|
||||
-- resist.lua) returns an empty bonus table for any race it does not
|
||||
-- recognize, exactly the same degrade an unlisted/future race token gets.
|
||||
-- Do not "fix" this by inventing a number -- add the real one once it is
|
||||
-- measured, the same way the five below were.
|
||||
-- ------------------------------------------------------------------
|
||||
local DWARF_RESIST = { [4] = 10 } -- frost
|
||||
local GNOME_RESIST = { [6] = 10 } -- arcane
|
||||
local NIGHTELF_RESIST = { [3] = 10 } -- nature
|
||||
local TAUREN_RESIST = { [3] = 10 } -- nature
|
||||
local UNDEAD_RESIST = { [5] = 10 } -- shadow
|
||||
|
||||
DC.RACE_RESIST = {
|
||||
["Dwarf"] = DWARF_RESIST,
|
||||
["Gnome"] = GNOME_RESIST,
|
||||
["Night Elf"] = NIGHTELF_RESIST,
|
||||
["NightElf"] = NIGHTELF_RESIST,
|
||||
["Tauren"] = TAUREN_RESIST,
|
||||
["Undead"] = UNDEAD_RESIST,
|
||||
["Scourge"] = UNDEAD_RESIST,
|
||||
}
|
||||
@@ -0,0 +1,171 @@
|
||||
-- DopingControl data/talenthit.lua
|
||||
-- The THIRD source of hit, next to gear and auras: TALENTS.
|
||||
--
|
||||
-- WHY ITS OWN FILE: gear hit is parsed from item tooltips, talent hit is
|
||||
-- looked up by talent NAME and rank. The two arrive over completely
|
||||
-- different paths -- GetTalentInfo for the own character, the server's
|
||||
-- inspect protocol (scan/talents.lua) for everyone else -- and only meet
|
||||
-- again in DC_Hit.TalentHit. Keeping the talent tables apart from
|
||||
-- data/hit.lua keeps that split visible in the file layout instead of
|
||||
-- hiding it inside one big table.
|
||||
--
|
||||
-- WHAT COUNTS AS SPELL HIT HERE: several talents are worded as "reduces
|
||||
-- the chance that the opponent can resist your <school> spells by X%".
|
||||
-- That is not a resistance mechanic -- it runs through the server's
|
||||
-- resist-miss spell modifier, i.e. it IS spell hit for that school, and it
|
||||
-- stacks with +hit from items. They are therefore modelled as
|
||||
-- school-specific spell hit and not as anything resistance-shaped.
|
||||
--
|
||||
-- BUCKETS -- an entry is
|
||||
-- { name = <talent name, EXACTLY as GetTalentInfo returns it>,
|
||||
-- tree = <talent tab name; documentation only, never matched on>,
|
||||
-- ranks = <max rank; the per-rank arrays must have this length>,
|
||||
-- melee = { <per rank> }, -- generic physical hit (melee column)
|
||||
-- ranged = { <per rank> }, -- generic ranged hit (ranged column)
|
||||
-- spell = { <per rank> }, -- GENERIC spell hit: every school
|
||||
-- offhand = { <per rank> }, -- OFF-HAND swings only, see below
|
||||
-- dwExtra = { <per rank> }, -- extra melee hit while DUAL WIELDING
|
||||
-- schools = { [<lowercase school id>] = { <per rank> } } }
|
||||
-- Every bucket is optional; an absent bucket contributes nothing. School
|
||||
-- ids are lowercase to match DC.SLOTS_HIT (core/const.lua); the
|
||||
-- CAPITALIZED tooltip spelling that gear parsing produces is bridged by
|
||||
-- DC.HIT_SCHOOL_KEY (data/hit.lua).
|
||||
--
|
||||
-- `offhand` is deliberately NOT part of the melee number. Off-hand hit
|
||||
-- applies to off-hand swings only, so adding it to the melee column would
|
||||
-- claim a main-hand hit chance the player does not have. It is carried so
|
||||
-- the cell tooltip can mention it.
|
||||
--
|
||||
-- MATCHING IS BY NAME: the reader (DC_Hit.ReadSelfTalents) hands in
|
||||
-- { [talent name] = rank }, because GetTalentInfo returns names, and
|
||||
-- tab/index positions shift whenever a tree is rebalanced while names
|
||||
-- almost never do. A talent this file does not list is ignored; a talent
|
||||
-- listed here that no longer exists simply never matches. Both directions
|
||||
-- degrade to "no talent hit counted", never to a wrong number.
|
||||
--
|
||||
-- English client assumed, like the tooltip patterns in data/hit.lua: on a
|
||||
-- localized client no name matches and the talent contribution reads 0.
|
||||
--
|
||||
-- WHAT IS DELIBERATELY EXCLUDED (each one grants "hit" in its tooltip text
|
||||
-- but not hit that belongs in any column of this tab):
|
||||
-- * PET hit -- hunter "Bestial Precision", warlock "Demonic Precision".
|
||||
-- They raise the PET's hit chance (and its weapon skill / inherited
|
||||
-- ratios); the player's own swings and casts are untouched.
|
||||
-- * ENEMY DEBUFFS -- rogue "Blinding Haze" LOWERS the hit chance of
|
||||
-- targets hit by Distract. It is the opposite sign and lands on
|
||||
-- someone else.
|
||||
-- * SINGLE-SPELL hit -- paladin "Improved Hand of Reckoning" buys hit
|
||||
-- for one ability only. A column that claimed it would overstate holy
|
||||
-- hit for every other spell the paladin casts.
|
||||
-- * WEAPON-TYPE hit -- mage "Wand Specialization" (wands) and priest
|
||||
-- "Wand Specialization" (wands and bows). Wand and bow attacks are
|
||||
-- neither generic spell hit nor -- for the priest -- the generic
|
||||
-- ranged-attack hit this tab's ranged column measures (that column
|
||||
-- only exists for a real ranged ATTACK weapon, see
|
||||
-- DC.RANGED_ATTACK_SUBTYPE), so counting them would misplace them.
|
||||
-- * TEMPORARY procs -- e.g. the second half of the shaman's Elemental
|
||||
-- Devastation, which grants spell hit for 10 sec after a melee crit.
|
||||
-- Only the permanent, always-on half is counted; a scan is a snapshot
|
||||
-- and a proc that happens to be up would make one row incomparable
|
||||
-- with the next.
|
||||
--
|
||||
-- Pure Lua 5.0, no WoW API -- dofile-loadable offline.
|
||||
|
||||
DopingControl = DopingControl or {}
|
||||
local DC = DopingControl
|
||||
|
||||
-- ------------------------------------------------------------------
|
||||
-- DC.TALENT_HIT -- [class token (UnitClass file token, e.g. "MAGE")] =
|
||||
-- array of talent entries. Classes with no hit talent at all are absent.
|
||||
-- ------------------------------------------------------------------
|
||||
DC.TALENT_HIT = {
|
||||
|
||||
-- Generic hit: melee AND every spell school, from ONE talent. The
|
||||
-- three entries below are the only talents in the game that raise
|
||||
-- both halves at once, which is why hybrids can be capped on both
|
||||
-- sides without spending a single item budget on it.
|
||||
DRUID = {
|
||||
{ name = "Natural Weapons", tree = "Balance", ranks = 3,
|
||||
melee = { 1, 2, 3 }, spell = { 1, 2, 3 } },
|
||||
},
|
||||
|
||||
PALADIN = {
|
||||
{ name = "Precision", tree = "Protection", ranks = 3,
|
||||
melee = { 1, 2, 3 }, spell = { 1, 2, 3 } },
|
||||
},
|
||||
|
||||
SHAMAN = {
|
||||
-- the talent's second half (melee crits grant 3/6/9% spell hit
|
||||
-- for 10 sec) is a TEMPORARY proc and is not counted -- see the
|
||||
-- exclusion list in the header
|
||||
{ name = "Elemental Devastation", tree = "Elemental", ranks = 3,
|
||||
melee = { 1, 2, 3 }, spell = { 1, 2, 3 } },
|
||||
},
|
||||
|
||||
-- Melee only.
|
||||
ROGUE = {
|
||||
-- "with melee weapons": no ranged half, even though rogues can
|
||||
-- carry a thrown weapon
|
||||
{ name = "Precision", tree = "Combat", ranks = 5,
|
||||
melee = { 1, 2, 3, 4, 5 } },
|
||||
},
|
||||
|
||||
HUNTER = {
|
||||
-- the talent says "hit chance" without naming a weapon, so the
|
||||
-- base half feeds the melee AND the ranged column.
|
||||
--
|
||||
-- dwExtra is the "and by an additional X% while dual wielding"
|
||||
-- half. The wording does not say which swings the EXTRA applies
|
||||
-- to; a dual-wield condition is a melee-build condition, so it is
|
||||
-- counted for MELEE ONLY. That is the reading that cannot
|
||||
-- overstate a hunter's ranged hit -- the number that decides
|
||||
-- whether the player is capped where it matters. Should the extra
|
||||
-- turn out to be generic, this under-reports ranged by up to 3%,
|
||||
-- which is the safe direction (same trade-off as the uncounted
|
||||
-- set bonuses in scan/hit.lua).
|
||||
{ name = "Surefooted", tree = "Survival", ranks = 3,
|
||||
melee = { 1, 2, 3 }, ranged = { 1, 2, 3 },
|
||||
dwExtra = { 1, 2, 3 } },
|
||||
},
|
||||
|
||||
-- Off-hand only.
|
||||
WARRIOR = {
|
||||
-- off-hand swings only: NOT added to the melee column, carried
|
||||
-- for the tooltip (see the `offhand` note in the header)
|
||||
{ name = "Dual Wield Specialization", tree = "Fury", ranks = 5,
|
||||
offhand = { 2, 4, 6, 8, 10 } },
|
||||
},
|
||||
|
||||
-- School-specific spell hit.
|
||||
MAGE = {
|
||||
{ name = "Arcane Focus", tree = "Arcane", ranks = 5,
|
||||
schools = { arcane = { 2, 4, 6, 8, 10 } } },
|
||||
-- one talent, two schools, full value in each: it is not split
|
||||
{ name = "Elemental Precision", tree = "Frost", ranks = 3,
|
||||
schools = { frost = { 2, 4, 6 }, fire = { 2, 4, 6 } } },
|
||||
},
|
||||
|
||||
PRIEST = {
|
||||
{ name = "Shadow Focus", tree = "Shadow", ranks = 5,
|
||||
schools = { shadow = { 2, 4, 6, 8, 10 } } },
|
||||
-- "your Holy and Discipline spells": Discipline is a SPELL LINE,
|
||||
-- not a magic school -- its damaging spells are holy school
|
||||
-- anyway, so the whole talent maps onto holy. There is no
|
||||
-- "discipline" column to put the other half in, and inventing one
|
||||
-- would be a school this client's damage model does not have.
|
||||
{ name = "Piercing Light", tree = "Discipline", ranks = 3,
|
||||
schools = { holy = { 2, 4, 6 } } },
|
||||
},
|
||||
|
||||
WARLOCK = {
|
||||
-- "your Affliction spells": another SPELL-LINE talent rather than
|
||||
-- a school talent. Affliction spells (Corruption, Curse of Agony,
|
||||
-- Drain Life, Siphon Life, Death Coil, ...) are shadow school, so
|
||||
-- the talent is mapped onto shadow. A warlock's fire spells
|
||||
-- (Immolate, Shadowburn's destruction siblings) get nothing from
|
||||
-- it, which is exactly why the fire and shadow columns of this tab
|
||||
-- can differ by 10% for the same character.
|
||||
{ name = "Suppression", tree = "Affliction", ranks = 5,
|
||||
schools = { shadow = { 2, 4, 6, 8, 10 } } },
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user