Kelsidavis-WoWee/include/pipeline/wowee_stat_curves.hpp
Kelsi 9a85cc029e feat(editor): add WSTM (Stat Modifier Curve) open catalog format
Open replacement for the gtChanceTo*.dbc / gtRegen*.dbc /
gtCombatRatings.dbc family of "1D level-keyed curve" tables.
Each entry defines a single linear curve mapping character level
to a stat value: melee crit chance per level, mana regen per
spirit per level, base armor per level, etc.

Curves are linear: value(level) = baseValue + perLevelDelta *
(level - 1), with the result optionally scaled by a global
multiplier and clamped to a level range. Most stock WoW curves
fit this shape — the few that don't (cubic Combat Ratings) live
in the dedicated WCRR catalog with spline support.

Distinct from WCRR (Combat Rating conversion, integer ratings ->
percentages) and WSPC (Spell Power Cost buckets, per-spell
costs). WSTM is for the generic engine-side stat curves that
aren't per-spell or per-rating.

Seven curveKind values classify the major stat families (Crit /
Hit / Power / Regen / Resist / Mitigation / Misc), and each
curve carries its own [minLevel, maxLevel] applicability range
plus a multiplier for global scaling without retuning each
curve's slope.

Three preset emitters: --gen-stm (5 crit-related curves with
canonical 3.3.5a base+per-level scaling — MeleeCrit 5%+0.05/lvl
resolves to 8.95% at lvl 80), --gen-stm-regen (4 regen curves
including ManaPerSpirit and the Vanilla-era 3 rage/sec OOC
decay), --gen-stm-armor (3 armor/mitigation/resistance curves).

The info renderer demos resolveAtLevel(curveId, 80) inline as
the @lvl80 column — server admins can sanity-check what each
curve resolves to at character cap without writing test code.

Validation enforces id+name presence, curveKind 0..6,
minLevel<=maxLevel, no duplicate ids; warns on:
  - maxLevel > 80 (unreachable at WotLK cap)
  - multiplier=0 (curve always evaluates to 0)
  - multiplier<0 (inverts the curve — possibly intentional)
  - perLevelDelta<0 (curve shrinks with level — unusual)

Wired through the cross-format table; WSTM appears in all 18
cross-format utilities. Format count 93 -> 94; CLI flag count
1076 -> 1081.
2026-05-10 00:05:07 -07:00

118 lines
4.1 KiB
C++

#pragma once
#include <cstdint>
#include <string>
#include <vector>
namespace wowee {
namespace pipeline {
// Wowee Open Stat Modifier Curve catalog (.wstm) —
// novel replacement for the gtChanceTo*.dbc / gtRegen*.dbc
// / gtCombatRatings.dbc family of "1D level-keyed curve"
// tables. Each entry defines a single linear curve mapping
// character level to a stat value: melee crit chance per
// level, mana regen per spirit per level, base armor per
// level, etc.
//
// Curves are simple linear: value(level) = baseValue +
// perLevelDelta * (level - 1), with the result optionally
// scaled by a global multiplier and clamped to a level
// range. Most stock WoW curves fit this shape — the few
// that don't (Combat Ratings) live in the dedicated WCRR
// catalog with cubic spline support.
//
// Distinct from WCRR (Combat Rating conversion) which
// converts integer rating points to percentages, and
// distinct from WSPC (Spell Power Cost buckets) which
// scales per-spell costs. WSTM is for the generic
// engine-side stat curves that aren't per-spell or
// per-rating.
//
// Cross-references with previously-added formats:
// None — this catalog is consumed directly by the
// character stat resolver. Engines look up curves by
// curveId or by name.
//
// Binary layout (little-endian):
// magic[4] = "WSTM"
// version (uint32) = current 1
// nameLen + name (catalog label)
// entryCount (uint32)
// entries (each):
// curveId (uint32)
// nameLen + name
// descLen + description
// curveKind (uint8) / minLevel (uint8) / maxLevel (uint8) / pad (uint8)
// baseValue (float)
// perLevelDelta (float)
// multiplier (float)
// iconColorRGBA (uint32)
struct WoweeStatCurve {
enum CurveKind : uint8_t {
Crit = 0, // crit chance scaling per level
Hit = 1, // hit chance scaling
Power = 2, // attack power / spell power growth
Regen = 3, // mana / health regen rates
Resist = 4, // resistance scaling
Mitigation = 5, // armor / damage reduction
Misc = 6, // catch-all
};
struct Entry {
uint32_t curveId = 0;
std::string name;
std::string description;
uint8_t curveKind = Misc;
uint8_t minLevel = 1;
uint8_t maxLevel = 80;
uint8_t pad0 = 0;
float baseValue = 0.0f;
float perLevelDelta = 0.0f;
float multiplier = 1.0f;
uint32_t iconColorRGBA = 0xFFFFFFFFu;
};
std::string name;
std::vector<Entry> entries;
bool isValid() const { return !entries.empty(); }
const Entry* findById(uint32_t curveId) const;
// Resolve the curve at the given character level,
// applying the linear formula then scaling by the
// multiplier and clamping to [minLevel..maxLevel].
// Returns 0.0 if the level is below minLevel.
float resolveAtLevel(uint32_t curveId, uint8_t level) const;
static const char* curveKindName(uint8_t k);
};
class WoweeStatCurveLoader {
public:
static bool save(const WoweeStatCurve& cat,
const std::string& basePath);
static WoweeStatCurve load(const std::string& basePath);
static bool exists(const std::string& basePath);
// Preset emitters used by --gen-stm* variants.
//
// makeCrit — 5 crit-related curves (MeleeCritChance,
// RangedCritChance, SpellCritChance,
// ParryChance, DodgeChance) covering
// the standard crit-tier scaling.
// makeRegen — 4 regen curves (ManaPerSpirit,
// HpPerSpirit, EnergyPerSec, RageDecay)
// with the canonical Vanilla/TBC/WotLK
// stock formulas.
// makeArmor — 3 armor / mitigation curves
// (BaseArmorPerLevel, ArmorMitigation,
// ResistancePerLevel).
static WoweeStatCurve makeCrit(const std::string& catalogName);
static WoweeStatCurve makeRegen(const std::string& catalogName);
static WoweeStatCurve makeArmor(const std::string& catalogName);
};
} // namespace pipeline
} // namespace wowee