feat(editor): add WSPV (Spell Variant) — 113th open format

Novel replacement for the implicit context-conditional
spell substitution rules vanilla WoW encoded across
SpellSpecificType, SpellEffect.EffectMechanic override
fields, and the proc-modified spell tables in
SpellProcEvent. Each entry binds one base spell to a
variant spell that activates when a runtime condition
is met (player in a specific stance, talent talented,
racial buff active, weapon equipped, aura present).

Six conditionKind values cover the full substitution
surface: Stance / Form / Talent / Race / EquippedWeapon
/ AuraActive. The conditionValue field is polymorphic —
its semantics depend on conditionKind (a stance spellId,
a talentId, a race bit, etc.). The spell-cast pipeline
iterates findByBaseSpell at cast time and picks the
highest-priority variant whose condition is satisfied,
falling through to the base spell if none matches.

Three preset emitters demonstrating the pattern:
makeWarriorStance (4 stance-conditional Warrior
variants — Heroic Strike Berserker damage bonus,
Battle baseline, Mocking Blow Defensive AoE taunt,
Pummel Berserker-only gate), makeTalentMod (4 talent-
modified variants — Frostbolt + Brain Freeze instant,
Lava Burst + Flame Shock auto-crit, Earth Shield +
Improved bonus heal, Ferocious Bite + Berserk),
makeRacial (4 race-gated racials — Stoneform Dwarf,
War Stomp Tauren, Berserking Troll, Will of the
Forsaken).

Validator's most novel check is the (baseSpell,
conditionKind, conditionValue, priority) 4-tuple
uniqueness — two variants with all four matching
would tie at runtime and resolve non-deterministically
(the spell-cast pipeline's std::sort by priority is
stable but the underlying iteration order is undefined
when priorities tie). Packs the tuple into 64 bits
(base 32 | value 16 | kind 8 | prio 8) for set lookup.

Format count 112 -> 113. CLI flag count 1213 -> 1218.
This commit is contained in:
Kelsi 2026-05-10 02:20:19 -07:00
parent 81eb854709
commit 6403d84a28
10 changed files with 725 additions and 0 deletions

View file

@ -0,0 +1,132 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Spell Variant catalog (.wspv) — novel
// replacement for the implicit context-conditional
// spell substitution rules vanilla WoW encoded across
// SpellSpecificType, SpellEffect.EffectMechanic
// override fields, and the proc-modified spell tables
// in SpellProcEvent. Each entry binds one base spell
// to a variant spell that activates when a runtime
// condition is met (player in a specific stance,
// talent talented, racial buff active, etc.).
//
// The spell-cast pipeline consults this catalog at the
// moment of cast: if any variant whose condition is
// satisfied has higher priority than the base, the
// variant spell ID is substituted for the cast.
//
// Cross-references with previously-added formats:
// WSPL: baseSpellId and variantSpellId both reference
// the WSPL spell catalog.
// WCMG: when conditionKind=Stance/Form, conditionValue
// is a WCMG spellId (the stance/form active
// on the caster).
// WTAL: when conditionKind=Talent, conditionValue is
// a WTAL talent ID (the talented passive that
// enables the variant).
// WCHC: when conditionKind=Race, conditionValue is a
// WCHC race bit.
//
// Binary layout (little-endian):
// magic[4] = "WSPV"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// variantId (uint32)
// nameLen + name
// descLen + description
// baseSpellId (uint32)
// variantSpellId (uint32)
// conditionKind (uint8) — Stance / Form /
// Talent / Race /
// EquippedWeapon /
// AuraActive
// priority (uint8) — higher overrides
// lower; 0 = base
// spell baseline
// pad0 (uint8) / pad1 (uint8)
// conditionValue (uint32) — polymorphic — see
// conditionKind
// iconColorRGBA (uint32)
struct WoweeSpellVariants {
enum ConditionKind : uint8_t {
Stance = 0, // value = stance spellId
Form = 1, // value = druid form
// spellId
Talent = 2, // value = talentId
Race = 3, // value = WCHC race bit
EquippedWeapon = 4, // value = weapon-type
// bitmask (sword=2,
// mace=4, etc.)
AuraActive = 5, // value = aura spellId
// currently on caster
};
struct Entry {
uint32_t variantId = 0;
std::string name;
std::string description;
uint32_t baseSpellId = 0;
uint32_t variantSpellId = 0;
uint8_t conditionKind = Stance;
uint8_t priority = 1;
uint8_t pad0 = 0;
uint8_t pad1 = 0;
uint32_t conditionValue = 0;
uint32_t iconColorRGBA = 0xFFFFFFFFu;
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t variantId) const;
// Returns all variants of one base spell, sorted by
// descending priority. The spell-cast pipeline
// iterates this list at cast time and picks the
// highest-priority variant whose condition is
// satisfied (or falls through to the base spell).
std::vector<const Entry*> findByBaseSpell(uint32_t baseSpellId) const;
};
class WoweeSpellVariantsLoader {
public:
static bool save(const WoweeSpellVariants& cat,
const std::string& basePath);
static WoweeSpellVariants load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-spv* variants.
//
// makeWarriorStance — 4 stance-conditional Warrior
// spell variants (Heroic Strike
// Battle vs Berserker damage
// bonus, Mocking Blow
// Defensive AoE, Pummel
// Berserker silence).
// makeTalentMod — 4 talent-modified spell
// variants (Frostbolt + Brain
// Freeze proc, Lava Burst +
// Flame Shock auto-crit,
// Earth Shield + Improved,
// Ferocious Bite + Berserk).
// makeRacial — 4 racial spell variants
// (Stoneform / War Stomp /
// Berserking / WoTF).
static WoweeSpellVariants makeWarriorStance(const std::string& catalogName);
static WoweeSpellVariants makeTalentMod(const std::string& catalogName);
static WoweeSpellVariants makeRacial(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee