promotional bannermobile promotional banner

LibItemDB

A library with all of the WoW items for use by other addons
Back to Files
File nameItemDB-ItemDB-v0.4.2.zip
Uploader
PmptastyPmptasty
Uploaded
Jul 19, 2026
Downloads
242
Size
18.2 MB
Flavors
ClassicMoP ClassicClassic TBC
File ID
8461505
Type
R
Release
Supported game versions
  • 5.5.3
  • 4.4.2
  • 3.4.5
  • 2.5.6
  • 1.15.8

What's new

Changelog

[v0.4.2] (2026-07-18) - Item sources, tank gear, weapon skill & more on-equip stats

New Features

  • Item source locations — every item now resolves to a real origin instead of a consumer falling back to "Other". GetSources previously knew only AtlasLoot boss drops; quest rewards, vendor items, crafted items, world drops, and PvP / reputation gear all read "Other". tools/build-item-sources.py reads the CMaNGOS vanilla server DB (the same SQLite the proc rates come from) plus wago Map / AreaTable / Faction names and derives, keyed by item ID:

    • quest — reward item ← quest_templatecreature_(questrelation|involvedrelation) NPC → the NPC's spawn map (an instance name), else the quest's ZoneOrSort zone. So Striker's Diadem (21366) resolves to Ahn'Qiraj Temple, not "Other".
    • vendornpc_vendor → the NPC's instance, else generic Vendor.
    • crafted — a create-item spell (spell_template Effect == 24) → its product → Crafted.
    • dropcreature_loot_template → the creature's instance map, else World Drop.
    • pvp — wago ItemSparse.RequiredPVPRank > 0PvP, overriding other origins (the rank is the source, so High Warlord's / Grand Marshal's read PvP).
    • reputation — wago ItemSparse.MinFactionID > 0 → the faction name (Argent Dawn, Cenarion Circle, The Defilers, …), added alongside the vendor zone. Continent maps (0/1/530/571) are skipped so an instance map always wins; battleground maps resolve by name (AV → Alterac Valley, WSG → Warsong Gulch, AB → Arathi Basin). Instance names are normalized to the AtlasLoot SourceNames spelling (NAME_ALIAS: wago's Ahn'Qiraj TempleTemple of Ahn'Qiraj, Ahn'QirajRuins of Ahn'Qiraj, DeadminesThe Deadmines, Stormwind StockadeThe Stockade) so the location and boss-drop graphs agree — a consumer needs no aliases and GetSources can dedup. Emitted to _core/ItemLocations.lua (lib:LoadItemLocations, { [id] = {{loc, kind}, …} }) — 8588 items on Vanilla: crafted 1669, drop 3470, quest 1987, vendor 1652, pvp 461, reputation 260. Source facts are from CMaNGOS (GPL-2.0), credited. Location: tools/build-item-sources.py.
  • GetSources enriched — merges the CMaNGOS locations under the AtlasLoot boss graph. Boss drops keep their full { instanceKey, instance, boss, encounterID, source = "atlasloot" } detail; each location adds { instance = <place>, source = <kind> } (no boss) for kinds quest / vendor / crafted / pvp / reputation / drop. Each place shows once: a location whose instance is already shown — by the boss graph, or by an earlier location (e.g. a ZG item that both drops and is a reputation reward in ZG) — is collapsed, while the boss graph's legitimate multiple-bosses-per-instance rows stand. HasSources is true when either source exists. New loader lib:LoadItemLocations, storage lib.itemLoc; wired into ItemDB.toc and core_ids' non-item skip list. Location: LibItemDB-1.0.lua, tools/itemdb_common.py.

  • Hand-curated "do not serve" list — some items have perfectly normal client data (a real name, a sane required level, real stats) yet were never made obtainable in Classic, so no build-hidden.py heuristic catches them and they'd rank as unreachable top-tier BiS. tools/curated_hidden.py (MANUAL_HIDDEN = { id: reason }) is the hand table for these; build-hidden.py merges it into each version's Hidden.lua on top of the automated name/level flags, so the items stay fully queryable for automation (GetInfo/GetLink/GetStats) and only ranking/search skip them — reusing the existing IsHidden / opts.includeHidden path, no lib or TOC change. First entries: Ashbringer (13262 — the original, never-released; Corrupted Ashbringer 22691 stays served) and Shard of the Defiler (17142). Only Vanilla currently loads a Hidden.lua (the ranking / search features are Vanilla-only), so that's where the flags take effect. Location: tools/curated_hidden.py, tools/build-hidden.py.

  • Life-steal & haste proc EP — two more proc effect types now score, extending the v0.4.1 proc pass. A life-steal proc ("Chance on hit: Steals 185 life", SPELL_EFFECT_HEALTH_LEECH) is enemy damage plus an equal self-heal, so it folds into the damage + heal buckets; a melee-haste proc (+X% attack speed for D s, MOD_MELEE_RANGED_HASTE) folds into damage as X% more white damage over the buff. Both use the weapon's own damage/speed and score through the existing DPS→AP path — no new lib buckets. Corrupted Ashbringer's leech now adds ~126 EP for a fury warrior instead of reading nothing. build-use-effects.py now also captures Delay (weapon speed) for the haste math. Of the procs still unscored, ~284 are genuinely un-scoreable (enemy debuffs / stuns / utility with no proc rate) and stay flagged for the BiS backstop. Location: tools/build-use-effects.py.

  • On-equip stats the walk missed now captured — tank avoidance, weapon skill, spell penetration, ranged haste. Vanilla delivers a whole class of stats as on-equip spell auras, not ItemSparse stat fields, so GetItemStats (and the walk) never saw them — every tank item was missing its defining stats, and famous items like Edgemaster's Handguards showed nothing. build-equip-stats.py now recovers them from the same on-equip aura chain it already used for hit/crit:

    • defense / dodge / parry / block — aura 30 (MOD_SKILL, misc 95) → DEFENSE, aura 49 → DODGE_PCT, 47 → PARRY_PCT, 51 → BLOCK_PCT.
    • weapon skill (endgame: +weapon skill vs a level-63 boss cuts miss/dodge/glancing and adds crit) — aura 30 with the 15 combat skill lines → WEAPON_SKILL_SWORD / _AXE / _DAGGER / _MACE / _2H_* / _POLEARM / _STAFF / _FIST / _UNARMED / _BOW / _GUN / _CROSSBOW / _THROWN. Profession lines (fishing/mining/…) are excluded.
    • spell penetration — aura 123 (stored negative) → ITEM_MOD_SPELL_PENETRATION (a key HawsJon already weights, so it scores for casters immediately — it was just never captured).
    • ranged haste — aura 557 (quivers / ammo pouches) → RANGED_HASTE_PCT.

    +309 items now carry these (EquipStats 760 → 1069), verified against Elementium Reinforced Bulwark (+7 defense), Dreadnaught Legplates (+13 defense / +1% dodge), Stronghold Gauntlets (+1% parry), Edgemaster's Handguards (+7 sword/axe/dagger), Trueaim Gauntlets (+8 bow/gun/crossbow), Enigma Robes (+20 spell pen). All merge into GetStats / Search and join WEIGHT_STATS (GetWeightStats, now 40 keys). The auras GetItemStats already surfaces (AP / spell power / mp5 / healing / resistance) stay excluded to avoid double-counting; non-stat auras (procs, movement, "+X vs Undead", damage shields) are skipped. Weapon skill is per-type and only helps the weapon you wield (a paperdoll applies the equipped line); HawsJon ships no weapon-skill weight, so it reads 0 until a scale provides one. Location: tools/build-equip-stats.py, LibItemDB-1.0.lua.

  • Weapon-skill EP scoring (formula-based). Capturing weapon skill isn't enough to rank it, and no data source ships a per-point weight — the value is non-linear and target-dependent (checked HawsJon, WoWSims presets, CMaNGOS, wago: none has one). So GetItemScore values it by porting the Classic attack-table math the way WoWSims implements it (sim/core/target.go): an item's +N skill for the weapon you wield converts to fewer misses (0.05 + skillDiff×0.002 above the 10-skill gap, else ×0.001), fewer dodges (×0.001), and stronger glancing blows (the 1.3 − 0.05×skillDiff / 1.2 − 0.03×skillDiff glance multipliers, whose clamp is the ~308 soft cap) — summed over current→current+N skill as a white-DPS gain, then DPS → AP → weight. It's context-aware via opts.attack = { weaponType, weaponSkill (current, default 300), weaponDPS (default 90), targetDefense (default 315 = a level-63 boss) }, and only counts the equipped weapon's type. Verified: Edgemaster's Handguards (+7) scores ~96 EP for a sword or dagger user at 300 skill, tapering to ~37 at 305 and ~9 at the 308 cap, and 0 for a mace user — a pure weapon-skill item that used to score ~1 EP now ranks like the BiS-tier item it is. Weapon-skill keys are formula-scored, so scoreStats skips them (no flat double-count); they stay in WEIGHT_STATS only for display/breakdown labels. Location: LibItemDB-1.0.lua (scoreWeaponSkill). Formula credited to WoWSims (MIT).

  • Block value captured; tank gear can now be EP-ranked. Added block value (aura 564 — Aegis of the Blood God +30, Drillborer Disk +23, the Dreadnaught set), completing the tank stat set (defense / dodge / parry / block% / block-value; 21 items). And since the shipped HawsJon scale is DPS/healer-only and weights none of those, tank gear couldn't be ranked at all — so a new curated "tank" weight scale (tools/build-tank-weights.py_core/TankWeights.lua, LoadBiSWeights("tank", …)) makes it rankable via GetItemScore(id, class, spec, {source="tank"}): warrior/paladin protection + druid feral-tank, survival-focused (Stamina-referenced). Verified: Elementium Reinforced Bulwark 80.9 (HawsJon) → 156.8 (tank). These weights are an APPROXIMATE curated default, not sim-derived — no source ships static tank weights — so tune them via LoadBiSWeights. Location: tools/build-equip-stats.py, tools/build-tank-weights.py.

  • Curated legendary sources — Thunderfury and Sulfuras (assembled from multiple pieces, so CMaNGOS's quest/loot graph can't place them) now resolve to Molten Core instead of "Other", via a CURATED_SOURCES override in build-item-sources.py. Extend it as more surface.

  • More test/placeholder items hiddenbuild-hidden.py's name filter now catches "test"/"Test"/"TEST" as a whole word anywhere (case-insensitive), not just all-caps or at the start, so generated junk like "2200 Test sword 80 purple", "Fast Test Polearm" and "Level 60 Test Gear …" (~190 more on Vanilla) no longer ranks or searches. Excludes the one real "High Test Eternium Fishing Line" (high-test = a genuine fishing-line term). Location: tools/build-hidden.py.

Notes

  • ~53% of core items now carry a location (up from boss drops alone). The rest is almost entirely low-level starter / vendor-trash gear (Worn Shortsword, Recruit's set) and hidden / unobtainable items — none of which surface in ranking or planning.
  • Vanilla only (CMaNGOS is a Vanilla server DB); TBC / Mists keep the AtlasLoot boss graph.
  • Feral attack power is now captured too. It's tooltip-only in the client (not a DB2 aura — an aura-99 scan finds nothing, which is why WoWSims scrapes it), and it's just 5 items in the whole game (Atiesh +420, The End of Dreams +305, Blessed Qiraji War Hammer +280, Hammer of Bestial Fury +154, Mace of Unending Life +140). Their values are lifted from WoWSims' parsed db.json (facts, credited) into FERAL_ATTACK_POWER, and — since a feral druid is always in form — aliased to ITEM_MOD_ATTACK_POWER_SHORT so it scores exactly like melee AP (verified: Blessed Qiraji War Hammer's +280 folds into the Attack Power row for a feral druid). Location: tools/build-equip-stats.py, LibItemDB-1.0.lua.
  • One stat stays uncaptured, as an honest follow-up: "+X damage vs creature type" (aura 102/131 — the captured value doesn't reconcile with the item tooltips, and 102 pairs with 131, so shipping it risks wrong/double-counted data).
  • Cleaned two unbalanced-assignments lint hints in scoreStats / scoreUseEffects by splitting the AP-accumulator declarations onto their own line (no behaviour change).

[v0.4.1] (2026-07-15) - On-use + proc EP scoring, EP breakdown & set-bonus API

New Features

  • On-use effect EP — a trinket's "Use: +280 Attack Power for 20s (2 min cd)" is a cast spell, not item stats, so a static-stat EP scored it ~0 even when it's the best trinket in the game (Earthstrike sorted to the bottom and got hidden by "upgrades only"). tools/build-use-effects.py extracts on-use effects from the DB2 chain (ItemEffect TriggerType 0 → SpellEffect direct stat auras → SpellMisc.DurationIndex / SpellDuration for length + ItemEffect.CoolDownMSec) → _core/UseEffects.lua (lib:LoadUseEffects, {k, m, d, cd} per item — 17 scored on Vanilla). GetItemScore folds in an uptime-averaged term, Σ magnitude × min(1, d/cd) × premium × weight (opts useEffects default on, usePremium default 1.3 for the controllable-burst bump). New reads: lib:GetUseEffect(id) (drives a "Use: +280 AP (20s / 2m)" display) and lib:HasUnscoredProc(id). Location: tools/build-use-effects.py, LibItemDB-1.0.lua.

  • Chance-on-hit & extra-attack proc EP — procs now score too. The missing piece — the proc rate — isn't in the client (ProcChance is a 100/101 sentinel), so it comes from the CMaNGOS vanilla server DB. tools/build-proc-rates.py reads CMaNGOS's SQLite world DB and resolves each item's rate the way the server does: item_template.spellppmRate (tuned PPM — Thunderfury 6, Ironfoe 0.8), else a real spell_template.ProcChance, else GetWeaponProcChance's default of 1.08 PPM for un-tuned "chance on hit" weapons (they all normalize to one constant PPM). Joined by item ID onto the effect read from the client → _core/UseEffects.lua (lib:LoadProcEffects / lib:GetProcEffect). GetItemScore prices each proc by bucket: buff (stat × uptime), damage (DPS → attack power at 14 AP = 1 DPS → weight; extra-attack procs like Ironfoe/Flurry Axe fold in as extraSwings × avg weapon damage), heal (HPS → healing weight), mana (→ mp5); rate is ppm/60 (weapon-speed-independent) or chance × hitsPerSec. 181 procs + 17 on-use score on Vanilla. Proc rates are facts from CMaNGOS (GPL-2.0), credited. Location: tools/build-proc-rates.py, build-use-effects.py, LibItemDB-1.0.lua.

  • GetSetBonusEPlib:GetSetBonusEP(setID, classID, spec){ [threshold] = ep }, the EP of each set-bonus stage (only the flat-stat ones — proc bonuses score 0 and are omitted). A consumer that knows how many pieces are equipped sums the stages it's crossed, which is the real set contribution — vs. the flat per-piece amortization GetItemScore(opts.setBonus) uses as a rough ranking proxy. Location: LibItemDB-1.0.lua.

  • GetItemScoreBreakdown — the itemized version of GetItemScore for a "why this EP" tooltip: { total, parts = { {kind, key, label, value, weight, ep}, … } }. Each contributing stat is its own row (value × weight = ep); set bonus, and every on-use and proc effect, are their own rows too. Guaranteed by construction — it calls the same scorers with the same opts — so total == GetItemScore(…) and Σ parts.ep == total. Generic Attack Power is one row (the winning melee/ranged, never both). The hot path (GetItemScore/ranking) is untouched — the scorers only allocate rows when a collector is passed. Location: LibItemDB-1.0.lua.

  • Scoring-model table — a separate _core/ScoreModel.lua (lib:LoadScoreModel / lib:GetScoreModel, tools/build-score-model.py) holds our MIT modeling constants — the on-use burst premium (moved out of hardcode), the per-class proc trigger rate (hitsPerSec; a paperdoll overrides at score time), and epPerHPS (heal-per-second → healing power) — kept separate from the licensed, immutable HawsJon weights in BiSWeights.lua so their data is never touched. Per-class overrides merge over a baked DEFAULT_MODEL. (Damage procs need no constant — DPS is priced as attack power via the fixed 14 AP = 1 DPS.)

Bug Fixes

  • Attack Power double-counted on generic-AP effects — a generic "+X Attack Power" is stored in DB2 as two auras (99 melee + 124 ranged), because in-game generic AP raises both attack types. Emitting both keys and summing scored the boost twice for any class whose melee and ranged AP weights are equal (a hunter), so Earthstrike read 52 instead of its true 26 and wrongly outranked always-on trinkets like Drake Fang Talisman. scoreStats / scoreUseEffects now take max(meleeAP, rangedAP) instead of summing — it auto-picks the attack type each class actually uses (ranged for a hunter, melee for a warrior). Passive GetItemStats reports generic AP once, so the walk path was never affected (0 _core items carry both keys). Location: LibItemDB-1.0.lua.

Known Gaps

  • Some procs stay flagged, by design — enemy debuffs (an armor/stat reduction on the target), pure-utility procs (stun, snare, dispel), and the rare proc with no rate in any CMaNGOS tier aren't scored; they're flagged (lib:HasUnscoredProc) so a consumer can caveat them and never hide a known-BiS item on a low score.
  • Extra-attack EP is a floor — it counts the weapon's base swing damage; a real extra swing also gets AP/crit scaling and can chain-proc, so the true value is higher.
  • Caster flat-damage procs price via attack power (14 AP = 1 DPS), not spell power — fine for physical classes, an approximation for casters. A follow-up.

[v0.4.0] (2026-07-05) - Drop sources, Best-in-Slot / EP, class & faction filtering

New Features

  • Drop sources — each item now carries the instance + boss that drops it, built offline from AtlasLootClassic and re-expressed in LibItemDB's own schema. The Classic client ships no Dungeon Journal, and the obvious client-DB2 fallback (the Retail Journal, read from wago.tools) was audited against AtlasLoot and found ~40% incomplete for Classic — missing whole Cata-revamped/restructured instances (Scholomance, Upper Blackrock Spire, Scarlet Monastery, Ragefire Chasm) and 10–25% of drops even on the un-revamped raids (Molten Core 77%, Blackwing Lair 91%) — and flat wrong-era for Onyxia / Zul'Gurub / Naxxramas. So AtlasLoot is the authoritative source. tools/build-sources.py parses AtlasLootClassic_DungeonsAndRaids/data.lua (data["Key"].items[].npcID + {slot, itemID} rows), intersects each itemID with the version's built _core (so a version only carries drops for content whose items it ships), and emits _core/Sources.lua (item→boss graph, boss→instance) + _core/SourceNames.lua (instance + boss display names). Coverage per version: 33 instances — every raid and dungeon plus a synthetic World Bosses instance (Kazzak, Azuregos, the four Emerald dragons): Vanilla 2,227 items / 306 bosses, TBC 2,240 / 308, Mists 2,248 / 307.

    We do not ship AtlasLoot's file — which-item-drops-from-which-boss is fact, not copyrightable expression, so only the facts are extracted; AtlasLootClassic (GPL-2.0, github.com/Hoizame/AtlasLootClassic) is credited in every generated header and in the README. Instance key is AtlasLoot's stable data key (a string, e.g. "MoltenCore"); encounter key is the boss npcID. core_ids() skips Sources* files (they key instances by ["Name"]=, not item IDs).

  • LibItemDB API — loaders lib:LoadSources(source, items, instances) and lib:LoadSourceNames({ inst=, enc= }) (instance keys may be strings); readers lib:GetSources(itemID){ { instanceKey, instance, boss, encounterID, source }, … }, lib:GetInstanceItems(instanceKey), lib:GetInstances(), lib:GetInstanceEncounters(instanceKey), plus lib:HasSources(id) / lib:HasSourceData(). An item that drops off several bosses/instances lists them all. Library MINOR bumped 5 → 6 (additive). Location: LibItemDB-1.0.lua.

  • Source audit tooltools/audit-sources.py cross-checks the shipped AtlasLoot data against the wago Retail Journal (an independent second opinion, not shipped) and reports where they disagree — candidate omissions to review. This is how the ~40% Journal-incompleteness above was measured.

  • Best-in-Slot (provider model) — the fixed BiS gear set per class/spec/phase, built from WoWSims (MIT, github.com/wowsims) ui/<spec>/gear_sets/*.gear.json by tools/build-sources.py's sibling tools/build-bis.py (positional 17-slot items array → slot key; phase from filename). Shipped as the "wowsims" provider; lib:LoadBiS(source, data) lets any addon or the user register/replace a list under another source name and pick it with lib:SetDefaultBiSSource. API: lib:GetBiS(classID, spec, phase), GetBiSItem, GetBiSSpecs, GetBiSPhases, IsBiS(itemID) (reverse). Vanilla: 42 sets across 8 classes. _core/BiS.lua.

  • EP ranking + per-raid BiS — the payoff of combining drops + weights + stats. tools/build-bis-weights.py extracts HawsJon's Classic stat weights (the community-standard set Pawn auto-installs on Classic) from ClassicHawsJon.lua (github.com/VgerMods/Pawn) — resolving each <Rating>Per level-60 conversion to a per-1% weight and mapping HawsJon's stat names to our keys → 37 specs / 9 classes in _core/BiSWeights.lua (lib:LoadBiSWeights, provider model). Values are used unchanged (immutable read-only "hawsjon" scale; players layer their own via LoadBiSWeights); HawsJon / Pawn / Vger credited (CC BY-NC-ND). (WoWSims' hardcoded epWeights were dropped — they're miscalibrated for Classic, e.g. Hunter Agility 0.64 vs Ranged AP 1.0 inverted, which sank agility gear like Ossirian's Binding to ~#30; with HawsJon it correctly ranks #2 of 701 belts.) EP weights now carry their own default ("hawsjon"), separate from the fixed-set default ("wowsims"); Get/SetDefaultBiSSource, GetBiSSources, GetBiSSpecs are weight-scale-scoped (what a player's in-game weight editor picks). Runtime scores Σ(stat × weight) over GetStats: lib:GetItemScore(itemID, classID, spec, opts), lib:RankItems(ids, …), lib:GetRaidBiS(instanceKey, classID, spec) (an instance's drops ranked — "what to want from this raid", opts.perSlot groups by slot), and lib:GetSlotRanking(classID, spec, slot) ("top N helms, in order"). Weapon DPS is weighted per hand (DPS_MAINHAND/OFFHAND/RANGED) resolved by equip slot; fragmented AP/mp5 keys are aliased. Verified: Warrior MC ranking leads with Bonereaver's Edge; Mage MC ranking is entirely different and correct.

  • On-equip stat enrichment — vanilla implements hit/crit/spell-hit/spell-crit as on-equip spell effects, not ItemSparse stat fields, so GetItemStats (and the walk) never reported them — leaving ranking blind to the stats that matter most. tools/build-equip-stats.py recovers them from the same DB2 chain build-effects.py uses (ItemEffect TriggerType 1 → SpellEffect auras 52 crit / 54 hit / 55 spell-hit / 552 spell-crit, value = base+1), emits _core/ EquipStats.lua, merged into GetStats/Search via lib:LoadEquipStats. Vanilla: 2,660 items (Lionheart Helm → CRIT_PCT=2, HIT_PCT=2, etc.).

  • Set bonuses — what naive scorers miss. tools/build-sets.py reads DB2 ItemSet (membership) + ItemSetSpell (threshold → spell) → SpellEffect, decoding flat-stat bonuses into our vocabulary (proc bonuses kept but empty) → _core/Sets.lua. API lib:GetItemSet(itemID), lib:GetSet(setID), lib:GetSetBonusStats(setID, pieces); GetItemScore(..., {setBonus=true}) adds a set piece's amortized bonus EP. Vanilla: 481 sets, 1,251 bonus thresholds (Devilsaur 2pc → +2% hit, Arcanist 3pc → +18 spell power, etc.).

  • Realm-aware data (Era vs SoD / Anniversary) — the 1.15 client's DB2 is a superset: original vanilla items (ID < 25000) plus the SoD / Fresh-Anniversary reissues and new items (ID >= 25000, mostly 200000+). Loading all of it mixed the two, so on an Era realm consumers saw duplicate / sourceless seasonal rows (e.g. "Crown of Destruction" as both 18817 and its reissue 228291). tools/split- seasonal.py now partitions each version's item-keyed data — base stays in place, the >= 25000 items move to Data/<Version>/_seasonal/ — and the overlay files self-guard on the new lib:IsSeasonalRealm() (C_Seasons.GetActiveSeason / C_SeasonInfo.GetCurrentDisplaySeasonID; seasons 2 SoD / 11 Fresh / 12 FreshHardcore). Era / Hardcore load original-Classic only; SoD / Fresh also load the overlay — automatically, so consumers need no season logic. No data is discarded. Vanilla: 17,595 base + 6,535 seasonal items. Also adds lib:GetSeason().

  • Faction filtering (Alliance / Horde) — faction-restricted gear isn't flagged by a race mask (AllowableRace is -1 for both "Highlander's" Alliance and "Defiler's" Horde items). tools/build-factions.py derives the side from three signals → _core/Factions.lua (lib:LoadFactions, restricted items only, neutral omitted): (1) ItemSparse.AllowableRace race mask (Alliance 77 / Horde 178); (2) a required reputation only one side can earn (MinFactionIDFaction.ReputationRaceMask_0); (3) PvP rank rewards — "Grand Marshal's ..." (Alliance) and "High Warlord's ..." (Horde) have identical DB2 rows (no faction field at all), so for items carrying a RequiredPVPRank the side is read from the faction-specific rank-title prefix (the two rank ladders are disjoint bar the shared rank-3 "Sergeant", left neutral) plus an explicit "Alliance"/"Horde" name word. This is faction-only — it never filters on the player's rank; a Horde player still sees every High Warlord's weapon whether or not they've earned it, only the Alliance ones drop out. 686 restricted items on Vanilla (was 240; +446 PvP rank rewards). The ranking APIs (GetRaidBiS / GetSlotRanking / RankItems) hide the other faction's items by default via UnitFactionGroup (opt out with opts.allFactions); lib:GetItemFaction(id) and lib:GetPlayerFaction() expose it. Also fixed core_ids() to skip the non-item-keyed _core files (Sources/SourceNames/BiS/BiSWeights/Sets/Factions).

  • Class filtering (a Rogue helm won't show for a Hunter) — class-restricted gear is flagged by ItemSparse.AllowableClass, but the dungeon sets (D1/D2), the AQ40 tier-2.5 sets, the Naxx T3 sets and Atiesh are mis-flagged as all-class — every Bonescythe (Rogue T3) piece and Atiesh come back usable by everyone (and T3 flags are even inconsistent within a set). tools/build-classes.py trusts AllowableClass where it's real (~1815 items) and corrects the mis-flagged sets via set membership (Bonescythe → Rogue, Cryptstalker → Hunter, …; 271 items across 36 curated sets) plus a 4-item map for Atiesh (marked usable by the caster classes) → _core/Classes.lua (lib:LoadClassRestrictions, mask bit = 1 << (classID-1); 2,090 restricted items on Vanilla). The ranking APIs hide gear the ranked class can't use by default (opt out opts.allClasses); lib:GetItemClasses(id) / lib:ClassUsable(id, classID) expose it. This is obtainability, not stats — a hunter can't do the rogue/caster quest, so the item never appears; the hunter's own Cryptstalker/Beaststalker/Giantstalker/Lok'delar still rank.

  • Same-name dedup in rankings — several item IDs can share one name and slot (the four class versions of Atiesh, a base item and its deprecated twin). Best-first rankings now keep only the highest-scoring one per (name, slot), which also auto-picks the class-appropriate variant (the caster Atiesh wins for a caster). Opt out with opts.keepDuplicates. Location: RankItems in LibItemDB-1.0.lua.

  • Hidden (test / placeholder / above-cap) items — the client DB2 carries items no player of the version can obtain or use: never-implemented junk ("90 Epic Rogue Belt", "[PH]" placeholders, "Test …", "Deprecated …") and unimplemented / future-expansion gear with a normal name but a required level above the cap (e.g. The Twin Blades of Azzinoth and the Warglaive of Azzinoth pair, RequiredLevel 70 in a level-60 game). These are kept in the DB for completeness but no longer served: tools/build-hidden.py flags them by two client-derivable signals — name pattern and RequiredLevel > the version's level cap (60/70/80/85/90, from wago ItemSparse) → _core/Hidden.lua (lib:LoadHidden, 927 on Vanilla: 910 by name + 17 by level, zero false positives — Atiesh/Thunderfury/Ashbringer stay). GetInfo/GetLink/GetStats/GetSources still resolve them, but RankItems/ GetRaidBiS/GetSlotRanking/Search skip them by default (opts.includeHidden to include); lib:IsHidden(id) exposes the flag. (The "This was never available to players" tooltip line is the ATT addon's curated list — not a Blizzard string nor in client data — so we use the client-derivable signals instead; ATT's full never-implemented list could layer on later for below-cap cases.)

Known Gaps

  • Wrath / Cata / Retail — no drop sources: those versions have no _core item data yet (Wrath/Cata are unshipped placeholders; Retail isn't shipped by this library). Sources build on top of _core, so they follow once those clients are walked and build-core / build-locales run.
  • Localization — instance and boss names are English-only for now (AtlasLoot's enUS keys), shipped locale-independent in _core/SourceNames.lua. Per-locale source names are a follow-up.
  • BiS / EP is Vanilla-only — drops cover Vanilla/TBC/Mists, but BiS sets, weights, equip-stat enrichment and set bonuses ship for Vanilla only so far; TBC/Mists are a build-bis*.py / build-equip-stats.py / build-sets.py run away (WoWSims has tbc-new / mop repos) once validated.
  • EP is approximate — a linear score (one opinion), not a full sim: stats we don't capture (haste, expertise, defense, dodge, parry, block, weapon speed, school-specific spell power) are unscored, so HawsJon weights for those stats are dropped and ordering can be off where they dominate. HawsJon covers all 9 classes × their specs (37 scales); WoWSims still supplies the fixed gear sets and leaves several healer/tank/paladin sets blank, so GetBiS is thinner there than the DPS specs (the EP ranking still works for them). No class weapon/armor-proficiency filter yet, so an off-class weapon can rank for a spec. Equip auras for defense/ dodge/parry (30/49/51) aren't decoded yet.
  • Seasonal overlay is coarse — the _seasonal split separates original-Classic from everything >= 25000, but doesn't yet distinguish SoD from Anniversary within that range (no clean DB2 flag was found — they share the 200000+ block), so a SoD or Fresh realm loads the whole overlay. And the seasonal items carry stats/names/sets but no sources or BiS yet (AtlasLoot-classic / WoWSims-classic don't cover them; WoWSims has a separate sod repo). Both are follow-ups.

[v0.3.0] (2026-06-01) - Consumable use-effect stats

New Features

  • Consumable use-effects — use-effect consumables (food, elixirs, flasks, potions, scrolls, jujus, …) now carry the stat buff they grant on use, which GetItemStats can't report. New offline pipeline tools/build-effects.py reads the static wago.tools DB2 chain: Item → ItemEffect (ParentItemID, on-use TriggerType) → SpellID → SpellEffect (Effect 6 = APPLY_AURA), following the EffectAura 23 periodic-trigger hop to the Well Fed spell for food. Auras are decoded to the SAME KEY=val vocabulary as gear stat blobs: MOD_STAT (29) → ITEM_MOD_<STAT>_SHORT (misc 0–4 = Str/Agi/Sta/Int/Spi, −1 = all), MOD_RESISTANCE (22, misc is a school bitmask) → RESISTANCE<n>_NAME, spell power / attack power / mp5 / healing → their ITEM_MOD_*, plus use-only keys CRIT_PCT / SPELL_CRIT_PCT / HEALTH / MANA. Value = EffectBasePoints + 1 (the Classic fixed-effect convention, verified against Elixir of the Mongoose +25 Agi/+2% crit, Flask of the Titans +1200 health, Tender Wolf Steak +12 Sta/+12 Spi). Inclusion is gated on InventoryType == 0 (non-equippable) so jujus (item class 12) are captured and gear/trinket on-use procs are excluded. Emitted locale-independent to Data/<Version>/_core/Effects.lua. Coverage: Vanilla 277, TBC 339, Mists 651 consumables. Location: tools/build-effects.py, Data/*/_core/Effects.lua, the three flavour .toc files.

  • LibItemDB APIlib:LoadEffects(t), lib:GetEffects(id) (just the use-effect buff stats), lib:HasEffects(id). The effect stats are merged into GetStats and Search (the stat filter and result stats), so a consumable is queryable by the stat it grants, alongside gear. Library MINOR bumped 4 → 5 (additive). Location: LibItemDB-1.0.lua.

Known Gaps

  • Mists combat ratings — MoP rating buffs (crit / haste / mastery, via EffectAura 189, a combat-rating bitmask) aren't decoded yet, so rating-only consumables on Mists are under-captured; Vanilla/TBC are flat-stat and fully covered. build-effects.py logs unmapped auras. Adding MOD_RATING (CR bit → ITEM_MOD_*_RATING) is the follow-up.

[v0.2.0] (2026-05-30) - TBC & Mists data + version-aware pipeline

New Features

  • TBC and Mists coverageData/TBC/ (30,032 items, captured on the Anniversary client build 2.5.5.67511) and Data/Mists/ (88,259 items, Classic progression build 5.5.3.67509), each in all 12 client languages with its random-suffix table. Joins the existing Vanilla data. Wrath and Cata have no live client yet (the Anniversary realms reach those phases later), so their .toc files stay empty placeholders until a client exists.

Improvements

  • Version-aware pipelinebuild-core.py and build-locales.py now take just the Version (e.g. build-core.py TBC) and resolve the rest from the installed clients: they read <WoW>/.build.info, map each client by its build MAJOR to a Version (1→Vanilla, 2→TBC, 3→Wrath, 4→Cata, 5→Mists), auto-locate that client's walk SavedVariables, and fetch classID + localized names + suffixes from the matching wago build. This tracks the Anniversary realm's progression automatically — when it advances to 3.4.x, build-core.py Wrath resolves to it with no code change. itemdb_common.py gained installed_clients / client_for_version / wago_build_for_version / togtools_sv; --sv / --wago-build remain as overrides.

[v0.1.0] (2026-05-30) - Stats, random suffixes, 12 languages, core/names split

New Features

  • Enriched data: item level + full stats — every item now carries its item level and the client's fully-aggregated stat set (the walk's GetItemStats, which includes spell power, mp5, spell healing/damage, resistances and armor — stats the raw wago stat columns omit). New API: GetStats, GetItemLevel, and stat-filtered Search (stat, minValue, minLevel, maxLevel), so e.g. "plate with strength" is answerable offline.
  • Random suffixes ("of the Bear") — modern Era encodes a random suffix as a positive ItemRandomProperties ID in field 8 of the item link (item:15218::::::1196). The DB ships those (name + exact stat lines per tier, from wago ItemRandomProperties + SpellItemEnchantment). New API: GetRandomProperty, GetRandomProperties, HasRandomProperty, BuildItemString, GetSuffixLink, ResolveSuffix, and suffix-aware ResolveName. BuildItemString(itemID, propID) reconstructs a link the game renders the full, correctly-scaled tooltip from — no stats are computed or stored for the scaling; the client does it.
  • All 12 client locales for VanilladeDE, enGB, enUS, esES, esMX, frFR, itIT, koKR, ptBR, ruRU, zhCN, zhTW. Names a locale hasn't translated fall back to English, so every language is complete (24,127 items, 2,033 suffix tiers).

Improvements

  • core/names storage split (LibStub MINOR → 4) — locale-independent data (quality, subclass, equip slot, item level, stats) now ships once in Data/<Version>/_core/<Class>.lua; only localized names ship per language in Data/<Version>/<locale>/Names.lua. New lib:LoadCore / lib:LoadNames (and lib:SetBuild); the old lib:LoadClasses is kept as a back-compat shim. This avoids duplicating the bulky numeric data across all 12 languages.
  • Pipeline rewrite — replaced tools/build-data.sh with tools/build-core.py (walk SavedVariables → _core/, classID joined from wago's Item table since the walk's serialization drops it) and tools/build-locales.py (wago ItemSparse.Display_lang → per-locale names with English fallback; suffix tables via the now locale-aware build-suffixes.py), sharing tools/itemdb_common.py. Stats come from the walk; classID + translated names come from wago.
  • Data/classic/Data/Vanilla/ to match the ProfessionDB layout; the Vanilla .toc lists the _core files (loaded once) plus every locale's self-guarded Names.lua + RandomProps.lua.

[v0.0.1] (2026-05-30) - Initial library

New Features

  • LibItemDB-1.0 — Standalone offline item database exposed as a LibStub library. Resolves item name ↔ id ↔ link and filters by type / subtype with no client cache required. API: IsReady, Count, GetMeta, GetName, GetInfo, GetQuality, GetLink, HasItem, GetID, GetClasses, GetSubClasses, Search, Iterate. Data is loaded via LoadClasses from shipped, pre-built data files (no end-user scan).
  • Classic (Era) enUS dataData/classic/enUS.lua, 24,069 items captured from client build 11508 via the TOG Tools Item DB builder. Bucketed by item class; each entry packs name · quality · subClassID · equipLoc.
  • Ships for every flavour except Retail — per-flavour TOCs for Vanilla/Era (11508), TBC (20505), Wrath (30405), Cataclysm (40402), and Mists (50503). A flavour TOC lists its Data/<version>/<locale>.lua line only once that capture exists; until then it loads the library with an empty database.
  • Guild version-checkItemDB.lua registers a { GetName = "LibItemDB", Version } host with VersionCheck-1.0 at PLAYER_LOGIN, so the library shows up in guild version checks like the rest of the TOG suite. Hard dependencies on Ace3 and VersionCheck-1.0 are declared in every .toc and in .pkgmeta (required-dependencies).

This mod has no additional files