Skip to main content

wowlab_engine_gamedata/game_data/
builder.rs

1// #t(file: rust_builder_conventions) resolver orchestration mutates this incremental accumulator through stable set_* and insert_* methods.
2
3//! [`ResolvedGameDataBuilder`]: incremental construction of [`ResolvedGameData`].
4
5use std::sync::Arc;
6
7use wowlab_types::{
8    data::WeaponStats,
9    sim::{EffectRef, SpellIdx},
10};
11
12use super::{
13    AuraProps, ConsumableSpells, PassiveSpellModifiers, PowerTypeProps, ResolvedCurves,
14    ResolvedGameData, ResolvedGameDataInner, ResolvedGameTables, SpellProps,
15};
16
17/// Builder for constructing [`ResolvedGameData`] incrementally.
18#[derive(Debug)]
19pub struct ResolvedGameDataBuilder {
20    inner: ResolvedGameDataInner,
21}
22
23macro_rules! effect_insert {
24    (effect $ty:ty; $($method:ident => $field:ident),+ $(,)?) => {
25        $(
26            pub fn $method(&mut self, effect: EffectRef, value: $ty) {
27                self.inner
28                    .effects.values
29                    .entry(effect)
30                    .or_default()
31                    .$field = value;
32            }
33        )+
34    };
35    (parts $ty:ty; $($method:ident => $field:ident),+ $(,)?) => {
36        $(
37            pub fn $method(&mut self, spell_id: SpellIdx, effect_index: u8, value: $ty) {
38                self.inner
39                    .effects.values
40                    .entry(EffectRef::new(spell_id, effect_index))
41                    .or_default()
42                    .$field = value;
43            }
44        )+
45    };
46}
47
48impl ResolvedGameDataBuilder {
49    pub(crate) fn new() -> Self {
50        Self {
51            inner: ResolvedGameDataInner::default(),
52        }
53    }
54
55    pub fn insert_implicit_targets(&mut self, effect: EffectRef, target_a: i32, target_b: i32) {
56        let values = self.inner.effects.values.entry(effect).or_default();
57
58        values.implicit_target_a = target_a;
59        values.implicit_target_b = target_b;
60    }
61
62    /// `EffectSpellClassMask` for an effect (1-based `effect_index`), used by affect-list matching.
63    pub fn insert_effect_class_mask(
64        &mut self,
65        spell_id: SpellIdx,
66        effect_index: u8,
67        mask: [i32; super::CLASS_FAMILY_FLAGS],
68    ) {
69        self.inner
70            .effects
71            .values
72            .entry(EffectRef::new(spell_id, effect_index))
73            .or_default()
74            .class_mask = mask;
75    }
76
77    pub fn insert_school_mask(&mut self, spell_id: SpellIdx, school_mask: i32) {
78        self.inner
79            .effects
80            .school_masks
81            .insert(spell_id, school_mask);
82    }
83
84    pub fn insert_spell_props(&mut self, spell_id: SpellIdx, props: SpellProps) {
85        self.inner
86            .spells
87            .props
88            .insert(spell_id, super::normalize_spell_modifier_defaults(props));
89    }
90
91    /// Overlay server-side `SpellProcEntry.AttributesMask` after client DBC resolution.
92    pub fn insert_server_proc_attributes(&mut self, spell_id: SpellIdx, attributes: u32) -> bool {
93        let Some(props) = self.inner.spells.props.get_mut(&spell_id) else {
94            return false;
95        };
96
97        props.proc_attributes = attributes;
98
99        true
100    }
101
102    pub fn insert_aura_props(&mut self, spell_id: SpellIdx, props: AuraProps) {
103        self.inner.spells.aura_props.insert(spell_id, props);
104    }
105
106    pub fn insert_power_type(&mut self, type_id: i32, props: PowerTypeProps) {
107        self.inner.spells.power_types.insert(type_id, props);
108    }
109
110    /// Mutable access to an inserted power type, for build-time passives (e.g. talent max/regen mods).
111    pub fn power_type_mut(&mut self, type_id: i32) -> Option<&mut PowerTypeProps> {
112        self.inner.spells.power_types.get_mut(&type_id)
113    }
114
115    pub fn insert_spell_override(&mut self, overridden_id: SpellIdx, replacement_id: SpellIdx) {
116        self.inner
117            .spells
118            .overrides
119            .insert(overridden_id, replacement_id);
120    }
121
122    pub fn insert_spell_learn(&mut self, teacher_id: SpellIdx, learned_id: SpellIdx) {
123        let learned = self.inner.spells.learns.entry(teacher_id).or_default();
124
125        if !learned.contains(&learned_id) {
126            learned.push(learned_id);
127        }
128    }
129
130    /// Store a spell's `SpellLabel` ids for affect-list gating (see [`ResolvedGameData::has_label`]).
131    pub fn insert_spell_labels(&mut self, spell_id: SpellIdx, labels: Vec<i32>) {
132        self.inner.spells.labels.insert(spell_id, labels);
133    }
134
135    /// Store a spell's class family (`SpellClassSet` + `SpellClassMask`) for affect-list matching.
136    pub fn insert_spell_class_flags(
137        &mut self,
138        spell_id: SpellIdx,
139        class_set: i32,
140        class_mask: [i32; super::CLASS_FAMILY_FLAGS],
141    ) {
142        self.inner.spells.class_flags.insert(
143            spell_id,
144            super::SpellClassFlags {
145                class_set,
146                class_mask,
147            },
148        );
149    }
150
151    pub fn set_armor_constant(&mut self, value: f64) {
152        self.inner.environment.armor_constant = value;
153    }
154
155    pub fn set_creature_armor(&mut self, value: f64) {
156        self.inner.environment.creature_armor = value;
157    }
158
159    pub fn set_target_armor_mult(&mut self, value: f64) {
160        self.inner.environment.target_armor_mult = value;
161    }
162
163    pub fn set_armor_constant_mod(&mut self, value: f64) {
164        self.inner.environment.armor_constant_mod = value;
165    }
166
167    pub fn set_main_hand(&mut self, value: WeaponStats) {
168        self.inner.environment.main_hand = value;
169    }
170
171    pub fn set_off_hand(&mut self, value: Option<WeaponStats>) {
172        self.inner.environment.off_hand = value;
173    }
174
175    pub fn set_curves(&mut self, value: ResolvedCurves) {
176        self.inner.scaling.curves = value;
177    }
178
179    pub fn set_game_tables(&mut self, value: ResolvedGameTables) {
180        self.inner.scaling.game_tables = value;
181    }
182
183    pub fn set_level(&mut self, value: u32) {
184        self.inner.scaling.level = value;
185    }
186
187    pub fn set_auto_attack_damage_mult(&mut self, value: f64) {
188        self.inner.passives.auto_attack_damage_mult = value;
189    }
190
191    pub fn set_pet_damage_mult(&mut self, value: f64) {
192        self.inner.passives.pet_damage_mult = value;
193    }
194
195    pub fn set_guardian_damage_mult(&mut self, value: f64) {
196        self.inner.passives.guardian_damage_mult = value;
197    }
198
199    pub fn set_specialization_spells(&mut self, value: Vec<SpellIdx>) {
200        self.inner.spells.specialization = value;
201    }
202
203    pub fn set_folded_passive_spells(&mut self, value: Vec<SpellIdx>) {
204        self.inner.passives.folded_spells = value;
205    }
206
207    pub fn set_class_target_debuffs(&mut self, value: Vec<SpellIdx>) {
208        self.inner.passives.class_target_debuffs = value;
209    }
210
211    pub fn set_consumable_spells(&mut self, value: ConsumableSpells) {
212        self.inner.environment.consumables = value;
213    }
214
215    /// Insert the complete neutral-default passive record for one spell.
216    pub fn insert_passive_spell_modifiers(
217        &mut self,
218        spell_id: SpellIdx,
219        modifiers: PassiveSpellModifiers,
220    ) {
221        self.inner
222            .passives
223            .spell_modifiers
224            .insert(spell_id, modifiers);
225    }
226
227    /// Mutate one field of a spell's consolidated passive record while retaining neutral defaults.
228    pub fn update_passive_spell_modifiers(
229        &mut self,
230        spell_id: SpellIdx,
231        update: impl FnOnce(&mut PassiveSpellModifiers),
232    ) {
233        update(
234            self.inner
235                .passives
236                .spell_modifiers
237                .entry(spell_id)
238                .or_default(),
239        );
240    }
241
242    /// Register NPC-filtered aura-382 attack/spell-power inheritance multipliers.
243    pub fn insert_pet_stat_inheritance_mult(
244        &mut self,
245        npc_id: u32,
246        attack_power_mult: f64,
247        spell_power_mult: f64,
248    ) {
249        self.inner
250            .passives
251            .pet_attack_power_inheritance
252            .insert(npc_id, attack_power_mult);
253        self.inner
254            .passives
255            .pet_spell_power_inheritance
256            .insert(npc_id, spell_power_mult);
257    }
258
259    /// Player-wide crit damage scaled by the caster's own crit chance, filtered by a DBC school mask.
260    pub fn push_crit_damage_per_crit_chance(&mut self, school_mask: i32, percent: f64) {
261        self.inner
262            .passives
263            .crit_damage_per_crit_chance
264            .push((school_mask, percent));
265    }
266
267    #[must_use]
268    pub fn build(mut self) -> ResolvedGameData {
269        for effect in self.inner.effects.values.keys() {
270            self.inner
271                .effects
272                .max_effect_index_by_spell
273                .entry(effect.spell)
274                .and_modify(|maximum| *maximum = (*maximum).max(effect.effect_index))
275                .or_insert(effect.effect_index);
276        }
277
278        for (effect, values) in &mut self.inner.effects.values {
279            values.source_class_set = self
280                .inner
281                .spells
282                .class_flags
283                .get(&effect.spell)
284                .map_or(0, |flags| flags.class_set);
285        }
286
287        ResolvedGameData {
288            inner: Arc::new(self.inner),
289        }
290    }
291
292    effect_insert! {
293        effect i32;
294        insert_scaling_class => scaling_class,
295        insert_chain_targets => chain_targets,
296        insert_effect_type => effect_type,
297        insert_effect_mechanic => effect_mechanic,
298        insert_effect_attributes => effect_attributes,
299        insert_effect_aura => effect_aura,
300        insert_trigger_spell => trigger_spell,
301        insert_effect_misc_value_0 => misc_value_0,
302        insert_effect_misc_value_1 => misc_value_1,
303        insert_shapeshift_form_flags => shapeshift_form_flags,
304    }
305
306    effect_insert! {
307        parts i32;
308        insert_shapeshift_combat_round_time_ms => shapeshift_combat_round_time_ms,
309    }
310
311    effect_insert! {
312        parts f64;
313        insert_ap_coef      => ap_coef,
314        insert_sp_coef      => sp_coef,
315        insert_base_points  => base_points,
316        insert_amplitude    => amplitude,
317        insert_period       => period,
318        insert_coefficient  => coefficient,
319        insert_points_per_resource => points_per_resource,
320        insert_chain_multiplier => chain_multiplier,
321        insert_effect_radius => radius,
322    }
323
324    effect_insert! {
325        effect f64;
326        insert_power_coefficient_mastery_add => power_coefficient_mastery_add,
327    }
328}