Skip to main content

wowlab_manifest_schema/
manifest.rs

1//! Root manifest documents and top-level sections.
2
3use std::collections::BTreeMap;
4
5use indexmap::IndexMap;
6use serde::Deserialize;
7
8use crate::{
9    AutoAttackDef, CURRENT_SCHEMA_VERSION, ManifestAuraDef, ManifestSpellDef, ScalarRef,
10    SpellGroupRule,
11};
12
13const fn default_schema_version() -> u32 {
14    CURRENT_SCHEMA_VERSION
15}
16
17/// Group of auras that share a runtime rule.
18#[derive(Debug, Deserialize)]
19pub struct SpellGroupDef {
20    pub rule: SpellGroupRule,
21    pub auras: Vec<String>,
22}
23
24/// `[mastery]` section: the spec's mastery spell and the hook path that applies it.
25#[derive(Debug, Deserialize)]
26pub struct MasterySection {
27    pub spell_id: u32,
28    pub hook: String,
29    #[serde(default)]
30    pub crit_damage_hook: Option<String>,
31}
32
33/// Top-level shape of `crates/engine/manifests/items.toml`.
34#[derive(Debug, Deserialize)]
35pub struct ItemsManifest {
36    #[serde(default = "default_schema_version")]
37    pub schema_version: u32,
38    #[serde(default)]
39    pub items: IndexMap<String, ItemManifestEntry>,
40}
41
42/// One entry in `[items.*]`.
43#[derive(Debug, Deserialize)]
44pub struct ItemManifestEntry {
45    pub id: u32,
46    #[serde(default)]
47    pub driver_spell_id: Option<u32>,
48    #[serde(default)]
49    pub auras: IndexMap<String, ManifestAuraDef>,
50    #[serde(default)]
51    pub spells: IndexMap<String, ManifestSpellDef>,
52    #[serde(default)]
53    pub effects: IndexMap<String, EffectRef>,
54    #[serde(default)]
55    pub player_cast_hook: Option<String>,
56    #[serde(default)]
57    pub player_impact_hook: Option<ItemImpactHookDef>,
58    #[serde(default)]
59    pub rppm: Option<f64>,
60    #[serde(default = "default_true")]
61    pub rppm_haste_scales: bool,
62}
63
64/// An item hook driven by landed player damage impacts.
65#[derive(Debug, Deserialize)]
66#[serde(deny_unknown_fields)]
67pub struct ItemImpactHookDef {
68    pub hook: String,
69    #[serde(default)]
70    pub periodic_only: bool,
71    #[serde(default)]
72    pub skip_periodic: bool,
73    #[serde(default)]
74    pub crit_only: bool,
75}
76
77pub(crate) const fn default_true() -> bool {
78    true
79}
80
81/// Fully composed shape of a `manifests/specs/<class>/<specialization>/manifest.toml` entry.
82// docref:start codegen-manifest
83#[derive(Debug, Deserialize)]
84#[serde(deny_unknown_fields)]
85pub struct Manifest {
86    #[serde(default = "default_schema_version")]
87    pub schema_version: u32,
88    pub spec: SpecSection,
89    pub mastery: MasterySection,
90    pub resource: ResourceSection,
91    #[serde(default)]
92    pub secondary_resource: Option<SecondaryResourceSection>,
93    #[serde(default)]
94    pub auras: IndexMap<String, ManifestAuraDef>,
95    #[serde(default)]
96    pub spells: IndexMap<String, ManifestSpellDef>,
97    #[serde(default)]
98    pub auto_attacks: IndexMap<String, AutoAttackDef>,
99    #[serde(default)]
100    pub talents: BTreeMap<String, u32>,
101    #[serde(default)]
102    pub set_bonuses: BTreeMap<String, u32>,
103    #[serde(default)]
104    pub talent_companion_auras: BTreeMap<String, Vec<String>>,
105    #[serde(default)]
106    pub talent_gated_aura_effects: Vec<TalentGatedAuraEffectDef>,
107    #[serde(default)]
108    pub metric_keys: Option<MetricKeysSection>,
109    #[serde(default)]
110    pub rotation_schema: Option<RotationSchema>,
111    #[serde(default)]
112    pub hero_talents: IndexMap<String, ManifestHeroTalentTree>,
113    #[serde(default)]
114    pub spell_groups: Vec<SpellGroupDef>,
115    #[serde(default)]
116    pub effects: IndexMap<String, EffectRef>,
117    #[serde(default)]
118    pub content_effects: IndexMap<String, ContentEffectCoverage>,
119    #[serde(default)]
120    pub reported_spells: BTreeMap<String, u32>,
121    #[serde(default)]
122    pub impact_procs: Vec<ImpactProcDef>,
123    #[serde(default)]
124    pub masked_passive_effects: Vec<MaskedPassiveEffectDef>,
125    #[serde(default)]
126    pub passive_effect_overrides: Vec<PassiveEffectOverrideDef>,
127}
128// docref:end codegen-manifest
129
130/// `[[masked_passive_effects]]` — one passive effect this specialization never receives.
131///   The shared static fold skips it, mirroring `SimC`'s per-spec `register_passive_effect_mask`
132///   and `deregister_passive_effect` calls.
133///   `reason` must cite the behaviour or the `sc_<class>.cpp` line that proves the exclusion.
134#[derive(Debug, Deserialize)]
135#[serde(deny_unknown_fields)]
136pub struct MaskedPassiveEffectDef {
137    pub spell_id: u32,
138    pub effect: u8,
139    pub reason: String,
140}
141
142/// `[[passive_effect_overrides]]` — the value one passive effect really applies at.
143///   This is `SimC`'s `player_t::register_passive_effect_override` for the shapes a spell's own
144///   description cannot state: a copy of another spell's effect, or a flat literal.
145///   Exactly one of `from` and `value` must be present, and `multiplier` scales either.
146///   `reason` must cite the `sc_<class>.cpp` line that proves the replacement.
147#[derive(Debug, Deserialize)]
148#[serde(deny_unknown_fields)]
149pub struct PassiveEffectOverrideDef {
150    pub spell_id: u32,
151    pub effect: u8,
152    #[serde(default)]
153    pub from: Option<EffectRef>,
154    #[serde(default)]
155    pub value: Option<f64>,
156    #[serde(default)]
157    pub multiplier: Option<f64>,
158    pub reason: String,
159}
160
161/// `[effects.<NAME>]` — a named `(spell_id, effect_index)` pair read by the spec hooks.
162#[derive(Debug, Deserialize)]
163#[serde(deny_unknown_fields)]
164pub struct EffectRef {
165    pub spell_id: u32,
166    pub effect: u8,
167}
168
169/// A `[[talent_gated_aura_effects]]` entry for a talent-conditional DBC aura effect.
170///
171/// The source coordinate identifies the already-resolved payload.
172/// `target_aura` optionally attaches that payload to a different scripted active window.
173/// Magnitude, modifier semantics, and affected-spell filters remain owned by game data.
174#[derive(Debug, Deserialize)]
175#[serde(deny_unknown_fields)]
176pub struct TalentGatedAuraEffectDef {
177    pub talent: String,
178    #[serde(default)]
179    pub target_aura: Option<String>,
180    #[serde(flatten)]
181    pub source: EffectRef,
182}
183
184/// Explicit coverage for an opaque spell effect that requires spec or item content.
185#[derive(Debug, Deserialize)]
186#[serde(deny_unknown_fields)]
187pub struct ContentEffectCoverage {
188    pub spell_id: u32,
189    pub effect: u8,
190    pub status: ContentEffectStatus,
191    pub reason: String,
192}
193
194/// How content accounts for an opaque DBC spell effect.
195#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
196#[serde(rename_all = "snake_case")]
197#[non_exhaustive]
198pub enum ContentEffectStatus {
199    Modeled,
200    Ignored,
201}
202
203#[derive(Debug, Deserialize)]
204pub struct ImpactProcDef {
205    #[serde(default)]
206    pub spell: Option<String>,
207    #[serde(default)]
208    pub auto_attack_hand: Option<String>,
209    #[serde(default)]
210    pub proc_type_mask_spell_id: Option<u32>,
211    #[serde(default)]
212    pub proc_type_mask_effect: Option<u8>,
213    #[serde(default)]
214    pub talent: Option<String>,
215    #[serde(default)]
216    pub hook: Option<String>,
217    pub chance_pct: ScalarRef,
218    #[serde(default)]
219    pub rng_model: ImpactProcRngDef,
220    #[serde(default)]
221    pub icd_ms: Option<ScalarRef>,
222    #[serde(default)]
223    pub target_icd_ms: u32,
224    #[serde(default)]
225    pub phase: Vec<ImpactProcPhaseDef>,
226    #[serde(default)]
227    pub hit_mask: Vec<ImpactProcHitDef>,
228    #[serde(default)]
229    pub charges: u8,
230    #[serde(default)]
231    pub periodic_only: bool,
232    #[serde(default)]
233    pub skip_periodic: bool,
234    #[serde(default)]
235    pub physical_only: bool,
236    #[serde(default)]
237    pub effects: Vec<EventEffectDef>,
238}
239
240#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
241#[serde(tag = "kind", rename_all = "snake_case")]
242#[non_exhaustive]
243pub enum ImpactProcRngDef {
244    #[default]
245    Fixed,
246    WeaponSpeed {
247        baseline_ms: u32,
248    },
249    Shuffled {
250        success_entries: u32,
251        total_entries: u32,
252    },
253    Accumulated {
254        #[serde(default)]
255        cap: u32,
256        #[serde(default)]
257        initial_count: u32,
258    },
259}
260
261#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
262#[serde(rename_all = "snake_case")]
263#[non_exhaustive]
264pub enum ImpactProcPhaseDef {
265    Cast,
266    Hit,
267    Finish,
268}
269
270#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
271#[serde(rename_all = "snake_case")]
272#[non_exhaustive]
273pub enum ImpactProcHitDef {
274    Normal,
275    Critical,
276    Miss,
277    Dodge,
278    Parry,
279    Glance,
280    Block,
281    Absorb,
282    Landed,
283}
284
285#[derive(Debug, Deserialize)]
286#[serde(tag = "kind", rename_all = "snake_case")]
287#[non_exhaustive]
288pub enum EventEffectDef {
289    Energize {
290        amount: ScalarRef,
291        #[serde(default)]
292        secondary: bool,
293    },
294    ExtendAura {
295        aura: String,
296        amount_ms: ScalarRef,
297    },
298}
299
300/// `[spec]` block identifying the spec.
301#[derive(Debug, Deserialize)]
302pub struct SpecSection {
303    pub id: u32,
304    #[serde(default)]
305    pub has_pet: bool,
306    #[serde(default)]
307    pub custom_handler: Option<String>,
308    #[serde(default)]
309    pub precombat_auras: Option<Vec<String>>,
310    #[serde(default)]
311    pub stealth_aura: Option<String>,
312}
313
314/// `[resource]` block describing the spec's primary resource; `max`/`regen`/`starts_at` derive from `game.power_types` when omitted.
315#[derive(Debug, Deserialize)]
316pub struct ResourceSection {
317    pub name: String,
318    #[serde(default)]
319    pub max: Option<f64>,
320    #[serde(default)]
321    pub regen: Option<f64>,
322    #[serde(default)]
323    pub starts_at: Option<f64>,
324    pub type_id: u8,
325}
326
327/// `[secondary_resource]` block describing the spec's secondary resource; `max` derives from `game.power_types` when omitted.
328#[derive(Debug, Deserialize)]
329pub struct SecondaryResourceSection {
330    pub name: String,
331    #[serde(default)]
332    pub max: Option<f64>,
333    pub type_id: u8,
334}
335
336/// Optional `[metric_keys]` block declaring the engine-side metric enum.
337#[derive(Debug, Deserialize)]
338pub struct MetricKeysSection {
339    pub module_name: String,
340    pub keys: Vec<String>,
341}
342
343/// Optional `[rotation_schema]` block listing rotation-state fields the spec exposes.
344#[derive(Debug, Deserialize)]
345pub struct RotationSchema {
346    #[serde(default)]
347    pub fields: Vec<String>,
348}
349
350/// One hero talent tree under `[hero_talents.<name>]`.
351#[derive(Debug, Deserialize)]
352pub struct ManifestHeroTalentTree {
353    #[serde(default)]
354    pub spells: BTreeMap<String, u32>,
355    #[serde(default)]
356    pub auras: BTreeMap<String, u32>,
357}