Files
Vampify/core/const.lua
T
2026-08-30 15:37:26 +02:00

750 lines
34 KiB
Lua

-- Vampify -- shared constants and the pinned formula descriptor.
--
-- The formula was MEASURED in-game (a companion telemetry addon's instrumentation, 2026-08-07..09), it is not derived from tooltips
-- or forum lore. It lives in a descriptor rather than inside the arithmetic so that a resolved
-- open question (spec 4.3) is a one-line change here, not a refactor of model.lua.
VampifyConst = {}
local C = VampifyConst
C.VERSION = "0.4.0"
-- Empty a list buffer for reuse. THE table.setn IS THE POINT: in Lua 5.0 table.insert maintains an
-- `n` field, and nil-ing the indices by hand does not reset it -- so the next round of inserts
-- lands BEHIND the stale n, leaving nil holes at 1..n. That crashed the options window
-- ("table.concat: table contains non-strings") on its second refresh and silently grew the tooltip
-- line buffer on every gear scan. One helper, so the trap exists in exactly one place.
function C.resetList(t)
for i = table.getn(t), 1, -1 do t[i] = nil end
table.setn(t, 0)
return t
end
-- Vampirism source spell ids: items are ranks 1-5, enchants are bracer/boots at +1% each.
C.SPELL_IDS = { 45420, 45421, 45422, 45423, 45424, 57146, 57148 }
-- ---- error capture -----------------------------------------------------------------------------
--
-- Two problems, one mechanism. A Lua error in an OnUpdate repeats every frame, which floods the
-- chat and makes the game unpleasant; and the developer only learns about it if the player thinks
-- to mention it. So: every error is written to <WoW>\imports\vampify_errors.txt where tooling
-- can read it, and only the FIRST occurrence of each distinct message reaches the chat. Repeats are
-- counted, not shown.
--
-- Lives here rather than in its own file purely so it loads first, before anything that could
-- throw, without costing a client restart to add a new file to the toc.
--
-- Deliberately chains to the previous handler for that first occurrence: swallowing errors outright
-- would trade a visible problem for an invisible one.
C._errSeen = {}
C._errOrder = {}
C._errPrev = nil
local function writeErrors()
if not ExportFile then return end -- SuperWoW only; harmless without it
local lines = {}
table.insert(lines, "addon=Vampify version=" .. tostring(C.VERSION))
for i = 1, table.getn(C._errOrder) do
local m = C._errOrder[i]
table.insert(lines, "[x" .. tostring(C._errSeen[m]) .. "] " .. m)
end
-- ExportFile appends .txt itself -- passing "vampify_errors.txt" would yield a double
-- extension, which is a known in-game gotcha.
ExportFile("vampify_errors", table.concat(lines, "\n"))
end
function C.installErrorCapture()
if C._errInstalled then return end
C._errInstalled = true
C._errPrev = geterrorhandler and geterrorhandler() or nil
seterrorhandler(function(msg)
local show, write = C.recordError(msg)
if write then writeErrors() end
if show and C._errPrev then C._errPrev(msg) end
end)
end
-- Pure counting half, split out so it can be tested offline: WoW's error handler cannot be, and a
-- bug in the thing that reports bugs is the worst kind. Returns (showInChat, writeToFile).
function C.recordError(msg)
msg = tostring(msg)
local firstTime = C._errSeen[msg] == nil
if firstTime then
C._errSeen[msg] = 1
table.insert(C._errOrder, msg)
else
C._errSeen[msg] = C._errSeen[msg] + 1
end
-- Show it once, then stay quiet: the player has been told, and a per-frame repeat adds nothing
-- but noise. Write on the first sighting and every 50th repeat, so a runaway loop stays visible
-- in the file without writing on every single frame.
local write = firstTime or math.mod(C._errSeen[msg], 50) == 0
return firstTime, write
end
-- Installed at file scope, not from an event: an error thrown while a later file is still loading
-- would otherwise be missed, and those are exactly the errors worth catching.
if seterrorhandler then C.installErrorCapture() end
-- ---- spells that do NOT trigger Vampirism -------------------------------------------------------
--
-- Damage shields were MEASURED not to trigger (Thorns 236 triggers, Thorium Shield Spike 27 -- zero
-- healing from either). Excluding the DAMAGE_SHIELD_SELF event is not enough: the client also
-- reports shield procs as ordinary SPELL_DAMAGE_EVENT_SELF, so they slipped in and were credited
-- with healing that never happened (spotted in-game via the per-ability breakdown, which showed
-- Thorium Shield Spike at 12.4%).
--
-- There is no reliable structural signal that marks a proc as a damage shield, so this is a list.
-- It is extendable at runtime with /vf exclude <id> for anything found later, and excluded spells
-- are dropped from BOTH the healing and the damage side -- counting damage that cannot trigger
-- would drag the headline percentage below the truth.
C.NO_TRIGGER = {
[16624] = "Thorium Shield Spike", -- id read from the live client 2026-08-10
}
function C.triggersVampirism(spellId)
if not spellId then return true end -- auto attacks always trigger
if C.NO_TRIGGER[spellId] then return false end
local cfg = VampifyConfig and VampifyConfig.getChar and VampifyConfig.getChar()
if cfg and cfg.noTrigger and cfg.noTrigger[spellId] then return false end
return true
end
-- ---- runtime exclusion management (cfg.noTrigger) -----------------------------------------------
--
-- cfg.noTrigger is the SAME mechanism as NO_TRIGGER above, made runtime-extendable: a new damage
-- shield is only ever found once it shows up wrongly in the breakdown (there is no structural
-- signal, see the comment above), so the list has to grow without a client restart. These three
-- functions are the one place that reads/writes cfg.noTrigger, so core/commands.lua's slash
-- handlers stay thin wiring and the union logic in triggersVampirism above is never duplicated.
--
-- New exclusions apply forward-only: they change what counts as triggering from the moment they
-- are added, but do not reach back into totals already recorded for earlier hits. See the /vf
-- exclude add command in commands.lua for the reasoning (kept there, next to where it is user-
-- visible); /vf reset remains the explicit way to discard a tainted session.
-- Adds spellId to the character-scoped runtime set. Returns false (and changes nothing) if the id
-- is already covered -- either hardcoded or already added -- with a reason string so the caller
-- can tell the player why, rather than silently re-adding.
function C.addNoTrigger(spellId, cfg)
if C.NO_TRIGGER[spellId] then return false, "builtin" end
if not cfg then return false, "no-config" end
if not cfg.noTrigger then cfg.noTrigger = {} end
if cfg.noTrigger[spellId] then return false, "already" end
cfg.noTrigger[spellId] = true
return true
end
-- Removes spellId from the runtime set. The hardcoded list is not reachable through this path --
-- it is measured fact, not a preference -- so this only ever touches cfg.noTrigger.
function C.removeNoTrigger(spellId, cfg)
if not (cfg and cfg.noTrigger and cfg.noTrigger[spellId]) then return false end
cfg.noTrigger[spellId] = nil
return true
end
-- Combined listing for /vf exclude list: hardcoded entries first, then runtime ones, each tagged
-- so the caller can say which is which. `out` is reused like every other list buffer in this addon
-- (see resetList above) so a repeated /vf exclude list stays allocation-free.
function C.listNoTrigger(cfg, out)
out = out or {}
C.resetList(out)
local n = 0
for id in pairs(C.NO_TRIGGER) do
n = n + 1
out[n] = { id = id, builtin = true }
end
if cfg and cfg.noTrigger then
for id in pairs(cfg.noTrigger) do
n = n + 1
out[n] = { id = id, builtin = false }
end
end
table.setn(out, n)
return out
end
-- ---- spell id -> name --------------------------------------------------------------------------
--
-- nampower hands us numeric spell ids; the tooltip has to show something a player recognises. 1.12
-- has no GetSpellInfo, but this client has two independent routes that do work, and the result is
-- cached because a tooltip refresh must not re-resolve on every frame.
--
-- Explicitly NOT tried: GameTooltip:SetHyperlink("spell:<id>"). It is documented broken on
-- 1.12/TurtleWoW -- it fails SILENTLY and leaves the tooltip empty -- so attempting it would cost
-- work per unknown id and yield nothing.
--
-- (Belongs in its own file; it is here so it loads first and needs no toc change, which would cost
-- a client restart. Move it when something else forces a restart anyway.)
C._spellNames = {}
C.spellNameSource = "none"
function C.spellName(id)
-- -1 is the aggregate's sentinel for auto attacks (VampifyAggregate.MELEE), which have no id.
if type(id) ~= "number" or id < 0 then return "Melee" end
local cached = C._spellNames[id]
if cached then return cached end
local name
-- 1. SuperWoW's SpellInfo(spellId). It reads the client's spell DBC by numeric id rather than
-- walking the spellbook, so it resolves foreign and NPC spells too, not just our own.
if SpellInfo then
local ok, n = pcall(SpellInfo, id)
if ok and type(n) == "string" and n ~= "" then
name = n
C.spellNameSource = "SpellInfo"
end
end
-- 2. nampower's own resolver, an independent second route in case SuperWoW is absent.
if not name and GetSpellNameAndRankForId then
local ok, n = pcall(GetSpellNameAndRankForId, id)
if ok and type(n) == "string" and n ~= "" then
name = n
C.spellNameSource = "nampower"
end
end
if not name then name = "Spell #" .. id end
C._spellNames[id] = name
return name
end
-- ---- spell id -> icon ---------------------------------------------------------------------------
--
-- The UI redesign's per-ability rows want an icon next to the name, not just text. SpellInfo(id)
-- already returns this as its 3rd value (name, rank, texture, minRange, maxRange) -- C.spellName
-- above discards it -- so this is a second thin reader over the SAME SuperWoW call, cached exactly
-- like C.spellName (a tooltip/bar refresh must not re-resolve on every frame), just with its own
-- cache table because the two calls can return a texture where the name lookup failed or vice versa
-- (unlikely, but nothing guarantees the two are correlated) and sharing a cache would only save one
-- pcall while entangling two independently-failing things for no reason.
--
-- Unlike C.spellName, a miss here has no readable-string fallback to manufacture -- "no icon" is a
-- real, displayable answer (the caller substitutes its own placeholder texture), so this returns nil
-- rather than inventing a path that does not exist on disk for a genuine SPELL id.
--
-- A.MELEE (-1) and any non-number id are NOT a SpellInfo lookup at all. Two revisions on
-- this, both 2026-08-24, in order:
-- (1) first: read the equipped main-hand weapon's texture directly (GetInventoryItemTexture) --
-- superseded below;
-- (2) then (this version): WoW's OWN spellbook already carries this mapping. Every character has
-- a base "Attack" entry in their spellbook, and its icon IS the equipped weapon's icon,
-- maintained by the client itself across a weapon swap (confirmed from an in-game spellbook
-- screenshot). Reading it through GetSpellTexture on the "Attack" entry therefore needs no
-- swap-invalidation machinery of its own -- WoW already does that job; see C.meleeIcon below
-- for exactly what is and is not independently confirmed about this here.
C.MELEE_ICON = "Interface\\Icons\\Ability_MeleeDamage" -- fixed fallback only, see C.meleeIcon
-- The "Attack" entry's SPELLBOOK INDEX, found once by NAME and cached -- an index is class- and
-- level-dependent (talent respecs, new abilities learned into earlier tabs can shift it), so it is
-- never hardcoded, exactly the same reasoning gui/display.lua's own buildSpellbookSet gives for
-- never assuming a fixed spell id. `_attackIndexKnown` distinguishes "not searched yet" (nil, look
-- again) from "searched, genuinely not found" (also nil, but do NOT re-scan every call -- that would
-- turn a one-time cost into a per-frame one for a locale/build where no "Attack" entry exists).
C._attackIndex = nil
C._attackIndexKnown = false
-- Invalidates the cached index -- wired to SPELLS_CHANGED/LEARNED_SPELL_IN_TAB below, the same two
-- events Blizzard's own FrameXML\SpellBookFrame.lua registers in 1.12.1 -- a newly learned ability
-- can insert into an earlier tab and shift every
-- later index, which is exactly the class of change that would silently point this at the wrong
-- spellbook row if never re-checked. Exposed (not local) so an offline test can simulate the event
-- without needing a real WoW event frame.
function C.invalidateAttackIndex()
C._attackIndex, C._attackIndexKnown = nil, false
end
-- Iterates the spellbook by index from 1, same loop shape as gui/display.lua's buildSpellbookSet
-- (GetSpellName(i, bookType) until nil) -- the established pattern in this codebase for "walk the
-- whole spellbook", not a second invention of it. pcall-wrapped like every other WoW API read in
-- this file: GetSpellName may be absent entirely (offline tests, or a hypothetical client without
-- it) and must degrade to "not found" rather than error.
local function findAttackIndex()
if not GetSpellName then return nil end
local bookType = BOOKTYPE_SPELL or "spell" -- literal fallback matches SpellBookFrame.lua's own
-- BOOKTYPE_SPELL = "spell" in case load order ever
-- left the global unset when this runs
local i = 1
while true do
local ok, name = pcall(GetSpellName, i, bookType)
if not ok or not name then break end
if name == "Attack" then return i end
i = i + 1
end
return nil
end
-- The "Attack" spellbook entry's current icon. The INDEX is cached (found by name, invalidated on
-- the events above); the TEXTURE itself is read FRESH on every call via GetSpellTexture, which is
-- what carries a weapon swap through without this file needing its own swap-invalidation -- WoW's
-- client is claimed to keep that entry's icon in sync with the equipped weapon on its own (confirmed
-- from an in-game screenshot of the "Attack" spellbook row).
--
-- NOT INDEPENDENTLY VERIFIED HERE: this file (and the FrameXML source it was checked against) can
-- only confirm the API SURFACE -- GetSpellName(i, bookType)/GetSpellTexture(i, bookType), bookType
-- BOOKTYPE_SPELL == "spell", both used exactly this way by Blizzard's own SpellBookFrame.lua. Whether
-- the "Attack" entry's texture is genuinely weapon-linked is a client-side (C++) behavior no Lua
-- source exposes -- that rests on in-game observation and is still open for an in-game
-- double-check (see this feature's own verdict).
--
-- Falls back to C.MELEE_ICON when there is no client API at all (offline tests), no "Attack" entry
-- was found (unknown locale/build), or GetSpellTexture itself returns nothing for it.
function C.meleeIcon()
if not C._attackIndexKnown then
C._attackIndex, C._attackIndexKnown = findAttackIndex(), true
end
if C._attackIndex and GetSpellTexture then
local bookType = BOOKTYPE_SPELL or "spell"
local ok, tex = pcall(GetSpellTexture, C._attackIndex, bookType)
if ok and type(tex) == "string" and tex ~= "" then return tex end
end
return C.MELEE_ICON
end
if CreateFrame then
local f = CreateFrame("Frame", "VampifyConstSpellbookWatcher")
f:RegisterEvent("SPELLS_CHANGED")
f:RegisterEvent("LEARNED_SPELL_IN_TAB")
f:SetScript("OnEvent", C.invalidateAttackIndex)
end
C._spellIcons = {}
function C.spellIcon(id)
if type(id) ~= "number" or id < 0 then return C.meleeIcon() end
local cached = C._spellIcons[id]
if cached ~= nil then
if cached == false then return nil end -- a cached MISS, not "never looked up"
return cached
end
local icon
if SpellInfo then
-- pcall-wrapped like C.spellName: SuperWoW may be absent, or SpellInfo may throw for an id
-- the client's DBC does not know. Either way this must degrade to nil, never propagate an
-- error into the hot damage/tooltip path.
local ok, _, _, tex = pcall(SpellInfo, id)
if ok and type(tex) == "string" and tex ~= "" then icon = tex end
end
-- Cache a MISS too (as `false`, distinguishable from "not yet looked up" = nil), so an id that
-- keeps failing (no SuperWoW, or genuinely no icon) is not re-queried every frame -- the whole
-- point of caching at all.
C._spellIcons[id] = icon or false
return icon
end
function C.errorCount()
local n = 0
for _, c in pairs(C._errSeen) do n = n + c end
return n, table.getn(C._errOrder)
end
C.FORMULA = {
-- AoE damping. Measured 0.7021 on Flame Wave, independent of the number of targets hit.
-- An earlier 0.86 estimate was wrong and is retracted.
aoeFactor = 0.7,
-- Per-hit floor equals the NUMBER OF SOURCES (3 items -> min 3 HP, 2 items -> exactly 2.000).
-- A fixed floor of 3, and no floor at all, are both refuted by measurement.
floorMode = "sources",
-- Settled 2026-08-10 (spec 4a): of 7239 own hits during active Vampirism epochs, 14 carried
-- mitigation and 9 were decisive after discarding full-health windows -- 7 pointed clearly at
-- net, one favoured gross by 0.6 HP (noise), one was a full-block window too noisy to count.
-- The gross error tracked mitigation * sumPercent almost exactly, which is the shape the
-- mechanism predicts. The server pays Vampirism on damage already reduced by block/absorb/
-- resist, not on the logged (pre-mitigation) amount.
damageBase = "net",
-- Three-valued. "undecided" behaves as "off" and makes the displayed total a LOWER BOUND.
channels = {
melee = "on", -- measured
spell = "on", -- measured
aoe = "on", -- measured, with aoeFactor
proc = "on", -- measured: Tidal Wave 12/12 windows, 0/36 control
dmgshield = "off", -- refuted: Thorns 236 + Thorium Spike 27 triggers -> 0 healing
dot = "on", -- measured 2026-08-10: ticks trigger per tick, no special
-- handling, measured in-game over two Flame Shock sequences.
-- The old "never occurred in a Vampirism epoch" note predated
-- that measurement.
totempet = "off", -- the developer's read from play; undecidable as instrumented
pvp = "undecided", -- no Vampirism-geared BG epoch with own damage
},
}
-- ---- item proc origins (spell id -> item name) --------------------------------------------------
--
-- C.ORIGINS itself is generated (see the block below) from the server-source
-- item_template + spell_template dump, in two layers: DIRECT spelltrigger_N == 2 ("chance on hit")
-- ids, which appear in the combat log exactly as-is; and CHAIN ids, reached by following item
-- spells at trigger 1 (on-equip proc auras) OR 2 one spell_template effect hop via
-- effectTriggerSpell1/2/3. The chain layer exists because a real proc can sit one hop away from
-- the item: spell 16614 ("Lightning Strike") never appears as an item_template spellid_N at all --
-- only its equip-aura wrapper (spell 16615, trigger==1) does, and 16615's own spell_template
-- effect is what fires 16614. A direct-only lookup would silently miss exactly that case. Plain
-- on-use procs (trigger 0) are still excluded -- they answer "what did this item grant", a
-- different question than a damage-origin lookup needs.
--
-- Nil-safe by construction: returns nil both when spellId has no known item origin AND when the
-- generator has never been run yet (C.ORIGINS absent entirely), so callers never need a separate
-- guard for "table not generated yet" vs. "id not a proc".
function C.itemProcOrigin(spellId)
if not C.ORIGINS then return nil end
return C.ORIGINS[spellId]
end
-- BEGIN GENERATED ORIGINS (generated by an offline generator script -- do not edit by hand)
--
-- Generated by an offline generator script. Two sources, unioned, two layers:
-- Pass 1 (item->spell edges): UNION of a live game-database dump of item-to-spell
-- links and a local item_template dump. Conflict rule: for an
-- item entry known to both, the live database wins outright (current server state); the local
-- dump contributes ONLY items the live database does not know at all. See this generator's file
-- header ("TWO SOURCES, UNION not replacement") for the full rule and why.
-- Pass 2 (spell effect chain): local spell_template dump -- the live database
-- has no equivalent effectTriggerSpell field, see this generator's file header.
-- 1. DIRECT: item-attached spell at trigger == 2 ("chance on hit") --
-- these appear in the combat log exactly as-is.
-- 2. CHAIN: item-attached spell at trigger == 1 (on-equip proc auras) OR == 2,
-- followed ONE spell_template effect hop via effectTriggerSpell1/2/3.
-- This exists because a real proc (spell 16614 "Lightning Strike") never
-- appears as an item-attached spell at all -- only the equip-aura wrapper
-- (16615) does, at trigger==1, and 16615's own spell_template effect is what
-- fires 16614.
-- A direct-only lookup silently misses cases exactly like this one.
-- An id reachable through both layers gets one entry with item names unioned.
--
-- Maps a proc spell id to the item(s) that carry it, so the addon can answer
-- "is this spell id an item proc, and from what" via a plain table lookup.
--
-- Lives in const.lua (not its own file) for the same reason C.spellName does: a new
-- .lua file needs a Vampify.toc entry, which costs a full client restart, while
-- const.lua loads first as the very first module and a table literal here needs only
-- a /reload. Re-run the generator and replace this block; do not hand-edit it.
--
-- Regenerate: run the offline generator script
C.ORIGINS = {
[56] = "Carved Ragetotem and others",
[89] = "Fire Sword of Crippling",
[695] = "Betrayer",
[744] = "Gift of the Spider God",
[772] = "Serrated Handaxe",
[871] = "Glaive of the Defender",
[1054] = "Lesser Firestone",
[2606] = "Stormfist",
[2912] = "Sentinel's Moonslicer",
[3264] = "Bloodhowler",
[3271] = "Chilton Wand",
[3396] = "The Ripper, Vile Sting",
[3424] = "Gift of the Spider God",
[3742] = "Gahz'rilla Fang, Aura Proc Damage Sword",
[5597] = "The Ripper",
[6647] = "The Ripper",
[6751] = "Venom Infused Blade",
[7712] = "Fiery Retributer and others",
[7714] = "Embergem Cuffs and others",
[8191] = "Sword of Zeal",
[8277] = "Cursed Shinbone",
[8313] = "Bite of Serra'kis",
[8348] = "Julie's Dagger",
[8552] = "Staff of Horrors, Cursed Thornblade",
[9057] = "Red Whelp Gloves, Helmet of the Scarlet Avenger",
[9159] = "Green Whelp Armor",
[9329] = "Soulstring and others",
[9632] = "Ravager, The Cruel Blade",
[9633] = "Ravager, The Cruel Blade",
[9777] = "Truesilver Breastplate",
[9796] = "Blight",
[9800] = "Truesilver Champion",
[9806] = "Phantom Blade",
[10342] = "Guardian Talisman",
[10351] = "Blade of the Basilisk",
[10368] = "Uther's Strength",
[10370] = "Mutilator",
[10371] = "Obedient Whacker",
[10373] = "Pendulum of Doom",
[11657] = "Jang'thraze the Protector",
[11658] = "Sul'thraze the Lasher",
[11790] = "Toxic Revenger",
[11791] = "Digmaster 5000, Vibroblade, Nail on a Plank",
[11879] = "Shoni's Disarming Tool, The Murkfisher",
[12484] = "Einhorn's Skinner",
[12685] = "Stealthblade",
[12686] = "Ragehammer",
[12731] = "Stoneslayer",
[13049] = "Dragon's Call",
[13318] = "Barman Shanker, Blood Talon, Killmaim",
[13438] = "Excavator's Brand",
[13439] = "Winter's Bite and others",
[13440] = "Grimclaw and others",
[13441] = "Orb of Fire",
[13442] = "Meteor Shard, Baron Charr's Sceptre, Scroll of Cow Portal",
[13480] = "Night Reaver",
[13482] = "The Ziggler, Electrocutioner Leg",
[13486] = "Bloodletter Scalpel, Fleshrender",
[13490] = "Howling Blade",
[13491] = "Iron Knuckles",
[13496] = "Mug O' Hurt",
[13518] = "Blackvenom Blade, Fang of the Broodmother",
[13519] = "Shortsword of Vengeance, Fishbringer",
[13524] = "Stalvan's Reaper",
[13526] = "Strike of the Hydra, Hookfang Shanker",
[13527] = "Supercharger Battle Axe",
[13528] = "Sword of Decay",
[13530] = "Tainted Pierce",
[13532] = "The Hand of Antu'sul",
[13533] = "The Jackhammer, Carved Grimtotem",
[13534] = "The Shatterer",
[13752] = "Dazzling Longsword",
[14106] = "The Black Knight",
[14118] = "Drakefang Butcher",
[14119] = "Phytoblade, Thunder 45, Thunderhorn",
[14126] = "Grim Reaper, Rusty Coghammer",
[15280] = "Dark Iron Sunderer",
[15283] = "Dark Iron Pulverizer",
[15494] = "Ironfoe",
[15592] = "Skaldrenox's Rage",
[15595] = "Force of Will",
[15601] = "Hand of Justice",
[15602] = "Lord General's Sword",
[15661] = "Terrorblade Glaive",
[15662] = "Smoldering Claw",
[16393] = "Glutton's Cleaver",
[16400] = "Widow's Kiss and others",
[16401] = "Poison-tipped Bone Spear",
[16403] = "The Goldtusk and others",
[16405] = "Ripsaw and others",
[16406] = "Gutwrencher, Hameya's Slayer",
[16407] = "Edge of Winter",
[16408] = "Darkwater Talwar",
[16409] = "Ghoulfang and others",
[16411] = "Deathblow",
[16413] = "Firebreather, Searing Blade",
[16414] = "Wraith Scythe, Scythe of the Harvest",
[16415] = "Taran Icebreaker",
[16433] = "Bloodfist and others",
[16454] = "Searing Needle",
[16528] = "Keris of Zul'Serak",
[16549] = "Blackhand Doomsaw",
[16551] = "Felstriker",
[16559] = "Flame Wrath",
[16560] = "Flame Wrath",
[16602] = "Blackblade of Shahram",
[16603] = "Demonfork",
[16608] = "Demon Forged Breastplate, Breastplate of the Dark Reaver",
[16614] = "Storm Gauntlets and others",
[16621] = "Invulnerable Mail",
[16782] = "Charged Servo Arm and others",
[16783] = "Totem of Infliction, Girdle of Reprisal",
[16784] = "Vile Protector",
[16871] = "Bleakwood Hew",
[16898] = "Blazing Rapier",
[16908] = "Serenity",
[16916] = "Arcanite Champion",
[16921] = "Masterwork Stormhammer, Thunderforge Lance",
[16927] = "Frostguard",
[16928] = "Annihilator",
[16939] = "Darkspear",
[17144] = "Stormpike",
[17148] = "Brain Hacker",
[17152] = "Destiny",
[17153] = "Kang the Decapitator",
[17154] = "The Green Tower",
[17196] = "Seeping Willow",
[17308] = "Femur Club and others",
[17315] = "Bashguuder, Rivenspike",
[17331] = "Fang of the Crystal Spider",
[17351] = "Argent Defender",
[17352] = "Argent Avenger",
[17407] = "The Nicker",
[17483] = "Demonshear",
[17484] = "Skullforge Reaver",
[17496] = "Crest of Retribution",
[17500] = "Malown's Slam",
[17504] = "Bloodrazor",
[17505] = "The Cruel Hand of Timmy",
[17506] = "Soul Breaker",
[17509] = "Dark Reaver",
[17510] = "Sword of Corruption",
[17511] = "Serpent Slicer, Ichor Spitter, Toxic Ripper",
[17936] = "Firestone",
[17940] = "Greater Firestone",
[17942] = "Major Firestone",
[18077] = "Venom Web Fang",
[18078] = "Bloody Pick and others",
[18081] = "Gryphon Rider's Stormhammer",
[18082] = "Volcanic Hammer",
[18083] = "Galgann's Firehammer",
[18084] = "Fist of the Damned",
[18086] = "Teebu's Blazing Longsword, Force of Magma",
[18088] = "Blade of the Wretched, Corruption",
[18089] = "Linken's Sword of Mastery",
[18090] = "Gutrender, Fisher's Harpoon, Ancient Hakkari Flayer",
[18091] = "Archeus",
[18092] = "Shiver Blade, Coldheart Icicle",
[18104] = "Axe of the Deep Woods",
[18107] = "Gut Ripper, Fleshslasher",
[18112] = "Ashbringer",
[18138] = "Black Duskwood Staff, Shadowblade, Deathbringer",
[18187] = "Pan of Po'rool, Overloaded Heating Coil, Fists of the Red Dawn",
[18197] = "Serpent's Kiss, Stinging Viper",
[18199] = "Burning War Axe",
[18200] = "Bloodspiller",
[18202] = "Bloodpike, Gargoyle Shredder Talons, Jaw of the Ancient",
[18203] = "Venomspitter",
[18204] = "Cobalt Crusher",
[18205] = "Black Malice and others",
[18206] = "Diabolic Skiver",
[18208] = "Scorpion Sting",
[18211] = "Nightblade, Doombringer, Ebon Hand",
[18214] = "Witchfury, Sun's Tail",
[18217] = "Duskbringer",
[18276] = "Darrowspike, Bonechill Hammer",
[18278] = "Silent Fang",
[18289] = "Gravestone War Axe",
[18350] = "Black Grasp of the Destroyer",
[18381] = "Cursed Felblade",
[18398] = "Sliverblade, Glacial Blade",
[18543] = "Everflame Torch",
[18633] = "Frightskull Shaft",
[18652] = "Barovian Family Sword",
[18656] = "Ebon Hilt of Marduk, Shadowbringer",
[18796] = "Fiery War Axe",
[18797] = "Flurry Axe",
[18798] = "Freezing Band",
[18803] = "Hand of Edward the Odd",
[18817] = "Skullflame Shield",
[18818] = "Skullflame Shield",
[18819] = "Archaic Slicer",
[18828] = "Wall of the Dead",
[18833] = "Alcor's Sunrazor",
[18946] = "The Lion Horn of Stormwind",
[18980] = "Electrified Gloves",
[19260] = "Chillpike",
[19755] = "Frightalon",
[19874] = "Shimmering Platinum Warhammer",
[20586] = "Windreaper",
[20869] = "Glacial Stone",
[20883] = "Joonho's Mercy",
[21140] = "Vis'kag the Bloodletter, Drake Talon Cleaver",
[21151] = "Gutgore Ripper",
[21152] = "Earthshaker",
[21153] = "Bonereaver's Edge",
[21159] = "Sulfuron Hammer",
[21162] = "Sulfuras, Hand of Ragnaros",
[21165] = "Empyrean Demolisher, Steamrigged Servohammer",
[21170] = "Shadowstrike",
[21179] = "Thunderstrike",
[21186] = "Spinal Reaper",
[21330] = "Eye of the Abyss",
[21898] = "Cowl of Terror",
[21919] = "Thrash Blade, Chronobreaker, Letashaz's Right Claw",
[21949] = "Gatorbite Axe",
[21951] = "Fist of Stone, Energized Spear",
[21952] = "Claw of Celebras",
[21961] = "Princess Theradras' Scepter, Carved Runetotem",
[21970] = "Mark of the Chosen",
[21992] = "Thunderfury, Blessed Blade of the Windseeker",
[22600] = "Force Reactive Disk",
[22619] = "Force Reactive Disk",
[22639] = "Eskhandar's Left Claw",
[22640] = "Eskhandar's Right Claw",
[22850] = "Quel'Serrar",
[22863] = "Sprinter's Sword",
[23267] = "Perdition's Blade",
[23454] = "Ironfist and others",
[23592] = "Electrified Dagger",
[23604] = "Black Amnesty",
[23605] = "Nightfall",
[23682] = "Darkmoon Card: Heroism",
[23684] = "Darkmoon Card: Blue Dragon",
[23687] = "Darkmoon Card: Maelstrom",
[23719] = "The Untamed Blade",
[24241] = "Halberd of Smiting",
[24251] = "Zulian Slicer",
[24254] = "Sceptre of Smiting",
[24257] = "Jeklik's Crusher",
[24362] = "Feralkin Necklace, Ancient Hakkari Flayer, Devilsaur Claws",
[24388] = "The Lobotomizer",
[24405] = "Glacial Spike, Scale of the Blue Drake",
[24585] = "Ancient Hakkari Manslayer",
[24993] = "Emerald Dragonfang",
[25768] = "Staff of the Qiraji Prophets",
[25907] = "Wrath of Cenarius",
[26108] = "Dark Edge of Insanity",
[26415] = "Kalimdor's Revenge",
[26693] = "Neretzek, The Blood Drinker, Shadowbringer, Pulseseeker",
[27039] = "Beastmaster's Cap, Deathmist Mask, Pendant of Kindred Spirit",
[27042] = "Beastmaster's Gloves, Deathmist Wraps",
[27205] = "Beastmaster's Boots, Deathmist Sandals, Charm of Dark Domination",
[27208] = "Beastmaster's Tunic, Deathmist Robe",
[27559] = "Hushblade, Jagged Obsidian Shield",
[27648] = "Thunderfury, Blessed Blade of the Windseeker",
[27655] = "Heart of Wyrmthalak",
[27657] = "Inflatable Woman",
[27860] = "Blade of Eternal Darkness",
[27868] = "Icemail Jerkin",
[28414] = "Corrupted Ashbringer",
[28441] = "Corrupted Ashbringer",
[28701] = "Tempest's Rage",
[29151] = "Misplaced Servo Arm",
[29155] = "Corrupted Ashbringer",
[29164] = "Stygian Buckler",
[29502] = "Hurricane",
[29638] = "Bow of Searing Arrows",
[29639] = "Dwarven Hand Cannon",
[29640] = "Heartseeking Crossbow, Lodestone",
[29641] = "Dark Iron Rifle",
[29644] = "Galgann's Fireblaster",
[29646] = "Quillshooter",
[29647] = "Shell Launcher Shotgun",
[29653] = "Venomstrike",
[29655] = "Verdant Keeper's Aim",
[45076] = "Aspect of Seradane",
[45416] = "Vial of Potent Venoms",
[45522] = "Idol of the Emerald Rot",
[45841] = "Rod of Resuscitation",
[45843] = "Mana Binding Signet",
[45848] = "Fist of the Forgotten Order",
[45849] = "Totem of Crackling Thunder",
[45856] = "Shawl of the Castellan",
[45858] = "Breath of Solnius",
[45860] = "Chromie's Broken Pocket Watch",
[45862] = "Libram of the Faithful",
[45867] = "Crystal of Vengeance",
[45869] = "Concentrated Power of Will",
[45873] = "Black Widow Eggs",
[45875] = "Vampire Heart",
[46104] = "Tempered Runeblade",
[46318] = "Demoralization Club",
[46319] = "Horde Defender's Axe",
[46431] = "Idol of Evergrowth",
[47354] = "Breastplate of Beast Mastery",
[48004] = "Dream's Herald",
[48005] = "Frostbound Slasher",
[48006] = "Pauldron of Deflection",
[48008] = "Bloodletter Razor",
[48048] = "Ornate Bloodstone Dagger",
[48101] = "Totem of the Stonebreaker",
[48102] = "Towerforge Demolisher",
[49369] = "Modrag'zan, Heart of the Mountain",
[51001] = "Ornate Pyrium Gauntlets",
[51144] = "Shar'tateth, the Shattered Edge",
[51250] = "Splinterspear Mace",
[51251] = "Claw of the Mageweaver",
[51266] = "Stonewrought Vambraces",
[51277] = "Crystalvein Breastplate",
[51740] = "Treant's Bane",
[52843] = "Bloodcaller's Decapitator",
[52853] = "Draenethyst Blade",
[52854] = "Draenethyst Juggernaut",
}
-- END GENERATED ORIGINS