feat(editor): add WSPR (Spell Reagent) — 80th open format milestone

Open replacement for the per-spell reagent fields in Spell.dbc
(Reagent[8] + ReagentCount[8]). Defines the item reagents that a
spell consumes from the caster's inventory each time it's cast —
Mage Portal needs a Rune of Portals, Resurrection needs a Holy
Candle (focused, not consumed), Warlock summons consume Soul
Shards.

One entry per reagent-using spell — most spells have no reagents
and are absent from this catalog. Each entry can list up to 8
(itemId, count) pairs which all must be present for the spell
to cast. Five reagentKind values capture the variety of reagent
semantics:
  - Standard      — ordinary consumed reagent
  - SoulShard     — warlock-specific shard tracking
  - FocusedItem   — required to cast but NOT consumed
                    (Symbol of Divinity for Resurrection)
  - Catalyst      — enables a stronger version of the spell
  - Tradeable     — crafting reagent for trade-skill recipes

Cross-references back to WSPL (every entry references a spellId)
and WIT (every reagent itemId references an item entry).
findBySpell(spellId) is the primary engine lookup.

Three preset emitters: --gen-spr (4 mage portal/teleport
reagents using Rune of Teleportation 17031), --gen-spr-warlock
(4 demon summons each consuming 1 Soul Shard 6265),
--gen-spr-rez (3 resurrection variants demonstrating each
ReagentKind including a no-reagent Druid Rebirth and a
focused-item Priest Resurrection).

Validation enforces id+name+spellId presence, reagentKind 0..4,
no duplicate ids; warns on:
  - slot itemId/count mismatch (id without count or vice versa)
  - SoulShard kind with non-canonical reagent (not item 6265)
  - FocusedItem kind with no reagent slots set (focused-item
    gating has nothing to gate)
  - duplicate spellId across entries (engine honors only first)

This is the 80th open format milestone. Wired through the
cross-format table; WSPR appears automatically in all 14
cross-format utilities. Format count 79 -> 80; CLI flag count
974 -> 979.
This commit is contained in:
Kelsi 2026-05-09 22:38:36 -07:00
parent 736ec3a1c0
commit d97f4bf5db
10 changed files with 704 additions and 0 deletions

View file

@ -0,0 +1,113 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Spell Reagent catalog (.wspr) — novel
// replacement for the per-spell reagent fields in
// Spell.dbc (Reagent[8] + ReagentCount[8]). Defines the
// item reagents that a spell consumes from the caster's
// inventory each time it's cast — Mage Portal needs a
// Rune of Portals, Resurrection needs an Ash of Eternity,
// Warlock summons consume Soul Shards.
//
// One entry per spell that has reagents — most spells
// have none and are absent from this catalog. Each entry
// can list up to 8 (itemId, count) pairs which all must
// be present for the spell to cast. The engine deducts
// them on cast resolution.
//
// Cross-references with previously-added formats:
// WSPL: spellId references the WSPL spell entry.
// WIT: every reagentItem*Id references a WIT item
// entry. The item's own consumeOnUse flag should
// be set (or the engine defaults to consuming
// since the spell explicitly references it).
//
// Binary layout (little-endian):
// magic[4] = "WSPR"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// reagentSetId (uint32)
// nameLen + name
// descLen + description
// spellId (uint32)
// reagentItemId[8] (uint32 each)
// reagentCount[8] (uint32 each)
// reagentKind (uint8) / pad[3]
// iconColorRGBA (uint32)
struct WoweeSpellReagent {
enum ReagentKind : uint8_t {
Standard = 0, // ordinary item reagent (most spells)
SoulShard = 1, // warlock-specific shard consumption
FocusedItem = 2, // not consumed; just required to cast
// (Symbol of Divinity for Resurrection)
Catalyst = 3, // one reagent enables stronger version
Tradeable = 4, // crafting reagent (Trade Skill recipes)
};
static constexpr int kMaxReagentSlots = 8;
struct Entry {
uint32_t reagentSetId = 0;
std::string name;
std::string description;
uint32_t spellId = 0;
uint32_t reagentItemId[kMaxReagentSlots] = {};
uint32_t reagentCount[kMaxReagentSlots] = {};
uint8_t reagentKind = Standard;
uint8_t pad0 = 0;
uint8_t pad1 = 0;
uint8_t pad2 = 0;
uint32_t iconColorRGBA = 0xFFFFFFFFu;
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t reagentSetId) const;
const Entry* findBySpell(uint32_t spellId) const;
// Count the slots actually used (slots whose itemId is
// non-zero). Most reagent sets use only 1-2 slots.
int usedSlotCount(uint32_t reagentSetId) const;
static const char* reagentKindName(uint8_t k);
};
class WoweeSpellReagentLoader {
public:
static bool save(const WoweeSpellReagent& cat,
const std::string& basePath);
static WoweeSpellReagent load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-spr* variants.
//
// makeMage — 4 mage portal/teleport reagents
// (Stormwind, Ironforge, Darnassus,
// Theramore) consuming Rune of
// Teleportation x1.
// makeWarlock — 4 warlock summons consuming Soul
// Shards (Voidwalker, Imp, Succubus,
// Felhunter) — each takes 1 shard.
// makeRez — 3 resurrection reagents (Ankh of
// Reincarnation, Ash of Eternity for
// priest mass-rez, druid Rebirth
// no-cost). Mix of Standard,
// FocusedItem, and no-reagent kinds.
static WoweeSpellReagent makeMage(const std::string& catalogName);
static WoweeSpellReagent makeWarlock(const std::string& catalogName);
static WoweeSpellReagent makeRez(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee