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.
132///   `reason` must explain the behavior or game-data basis for the exclusion.
133#[derive(Debug, Deserialize)]
134#[serde(deny_unknown_fields)]
135pub struct MaskedPassiveEffectDef {
136    pub spell_id: u32,
137    pub effect: u8,
138    pub reason: String,
139}
140
141/// `[[passive_effect_overrides]]` — the value one passive effect really applies at.
142///   This covers values a spell's own description cannot state: a copy of another spell's effect,
143///   or a flat literal.
144///   Exactly one of `from` and `value` must be present, and `multiplier` scales either.
145///   `reason` must explain the behavior or game-data basis for the replacement.
146#[derive(Debug, Deserialize)]
147#[serde(deny_unknown_fields)]
148pub struct PassiveEffectOverrideDef {
149    pub spell_id: u32,
150    pub effect: u8,
151    #[serde(default)]
152    pub from: Option<EffectRef>,
153    #[serde(default)]
154    pub value: Option<f64>,
155    #[serde(default)]
156    pub multiplier: Option<f64>,
157    pub reason: String,
158}
159
160/// `[effects.<NAME>]` — a named `(spell_id, effect_index)` pair read by the spec hooks.
161#[derive(Debug, Deserialize)]
162#[serde(deny_unknown_fields)]
163pub struct EffectRef {
164    pub spell_id: u32,
165    pub effect: u8,
166}
167
168/// A `[[talent_gated_aura_effects]]` entry for a talent-conditional DBC aura effect.
169///
170/// The source coordinate identifies the already-resolved payload.
171/// `target_aura` optionally attaches that payload to a different scripted active window.
172/// Magnitude, modifier semantics, and affected-spell filters remain owned by game data.
173#[derive(Debug, Deserialize)]
174#[serde(deny_unknown_fields)]
175pub struct TalentGatedAuraEffectDef {
176    pub talent: String,
177    #[serde(default)]
178    pub target_aura: Option<String>,
179    #[serde(flatten)]
180    pub source: EffectRef,
181}
182
183/// Explicit coverage for an opaque spell effect that requires spec or item content.
184#[derive(Debug, Deserialize)]
185#[serde(deny_unknown_fields)]
186pub struct ContentEffectCoverage {
187    pub spell_id: u32,
188    pub effect: u8,
189    pub status: ContentEffectStatus,
190    pub reason: String,
191}
192
193/// How content accounts for an opaque DBC spell effect.
194#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
195#[serde(rename_all = "snake_case")]
196#[non_exhaustive]
197pub enum ContentEffectStatus {
198    Modeled,
199    Ignored,
200}
201
202#[derive(Debug, Deserialize)]
203pub struct ImpactProcDef {
204    #[serde(default)]
205    pub spell: Option<String>,
206    #[serde(default)]
207    pub auto_attack_hand: Option<String>,
208    #[serde(default)]
209    pub proc_type_mask_spell_id: Option<u32>,
210    #[serde(default)]
211    pub proc_type_mask_effect: Option<u8>,
212    #[serde(default)]
213    pub talent: Option<String>,
214    #[serde(default)]
215    pub hook: Option<String>,
216    pub chance_pct: ScalarRef,
217    #[serde(default)]
218    pub rng_model: ImpactProcRngDef,
219    #[serde(default)]
220    pub icd_ms: Option<ScalarRef>,
221    #[serde(default)]
222    pub target_icd_ms: u32,
223    #[serde(default)]
224    pub phase: Vec<ImpactProcPhaseDef>,
225    #[serde(default)]
226    pub hit_mask: Vec<ImpactProcHitDef>,
227    #[serde(default)]
228    pub charges: u8,
229    #[serde(default)]
230    pub periodic_only: bool,
231    #[serde(default)]
232    pub skip_periodic: bool,
233    #[serde(default)]
234    pub physical_only: bool,
235    #[serde(default)]
236    pub effects: Vec<EventEffectDef>,
237}
238
239#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, PartialEq)]
240#[serde(tag = "kind", rename_all = "snake_case")]
241#[non_exhaustive]
242pub enum ImpactProcRngDef {
243    #[default]
244    Fixed,
245    WeaponSpeed {
246        baseline_ms: u32,
247    },
248    Shuffled {
249        success_entries: u32,
250        total_entries: u32,
251    },
252    Accumulated {
253        #[serde(default)]
254        cap: u32,
255        #[serde(default)]
256        initial_count: u32,
257    },
258}
259
260#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
261#[serde(rename_all = "snake_case")]
262#[non_exhaustive]
263pub enum ImpactProcPhaseDef {
264    Cast,
265    Hit,
266    Finish,
267}
268
269#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq)]
270#[serde(rename_all = "snake_case")]
271#[non_exhaustive]
272pub enum ImpactProcHitDef {
273    Normal,
274    Critical,
275    Miss,
276    Dodge,
277    Parry,
278    Glance,
279    Block,
280    Absorb,
281    Landed,
282}
283
284#[derive(Debug, Deserialize)]
285#[serde(tag = "kind", rename_all = "snake_case")]
286#[non_exhaustive]
287pub enum EventEffectDef {
288    Energize {
289        amount: ScalarRef,
290        #[serde(default)]
291        secondary: bool,
292    },
293    ExtendAura {
294        aura: String,
295        amount_ms: ScalarRef,
296    },
297}
298
299/// `[spec]` block identifying the spec.
300#[derive(Debug, Deserialize)]
301pub struct SpecSection {
302    pub id: u32,
303    #[serde(default)]
304    pub has_pet: bool,
305    #[serde(default)]
306    pub custom_handler: Option<String>,
307    #[serde(default)]
308    pub precombat_auras: Option<Vec<String>>,
309    #[serde(default)]
310    pub stealth_aura: Option<String>,
311}
312
313/// `[resource]` block describing the spec's primary resource; `max`/`regen`/`starts_at` derive from `game.power_types` when omitted.
314#[derive(Debug, Deserialize)]
315pub struct ResourceSection {
316    pub name: String,
317    #[serde(default)]
318    pub max: Option<f64>,
319    #[serde(default)]
320    pub regen: Option<f64>,
321    #[serde(default)]
322    pub starts_at: Option<f64>,
323    pub type_id: u8,
324}
325
326/// `[secondary_resource]` block describing the spec's secondary resource; `max` derives from `game.power_types` when omitted.
327#[derive(Debug, Deserialize)]
328pub struct SecondaryResourceSection {
329    pub name: String,
330    #[serde(default)]
331    pub max: Option<f64>,
332    pub type_id: u8,
333}
334
335/// Optional `[metric_keys]` block declaring the engine-side metric enum.
336#[derive(Debug, Deserialize)]
337pub struct MetricKeysSection {
338    pub module_name: String,
339    pub keys: Vec<String>,
340}
341
342/// Optional `[rotation_schema]` block listing rotation-state fields the spec exposes.
343#[derive(Debug, Deserialize)]
344pub struct RotationSchema {
345    #[serde(default)]
346    pub fields: Vec<String>,
347}
348
349/// One hero talent tree under `[hero_talents.<name>]`.
350#[derive(Debug, Deserialize)]
351pub struct ManifestHeroTalentTree {
352    #[serde(default)]
353    pub spells: BTreeMap<String, u32>,
354    #[serde(default)]
355    pub auras: BTreeMap<String, u32>,
356}