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, PowerTypeProps, ResolvedCurves, ResolvedGameData,
14    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    pub fn insert_direct_damage_mult(&mut self, spell_id: SpellIdx, mult: f64) {
216        self.inner
217            .passives
218            .direct_damage_mult
219            .insert(spell_id, mult);
220    }
221
222    pub fn insert_direct_damage_flat(
223        &mut self,
224        spell_id: SpellIdx,
225        amount: f64,
226        mastery_coefficient: f64,
227    ) {
228        self.inner
229            .passives
230            .direct_damage_flat
231            .insert(spell_id, amount);
232        self.inner
233            .passives
234            .direct_damage_flat_mastery
235            .insert(spell_id, mastery_coefficient);
236    }
237
238    pub fn insert_periodic_damage_mult(&mut self, spell_id: SpellIdx, mult: f64) {
239        self.inner
240            .passives
241            .periodic_damage_mult
242            .insert(spell_id, mult);
243    }
244
245    /// Static flat crit-chance bonus (percent points) for a spell from passive spec/class auras.
246    pub fn insert_crit_chance_bonus(&mut self, spell_id: SpellIdx, bonus: f64) {
247        self.inner
248            .passives
249            .crit_chance_bonus
250            .insert(spell_id, bonus);
251    }
252
253    pub fn insert_crit_chance_mult(&mut self, spell_id: SpellIdx, mult: f64) {
254        self.inner.passives.crit_chance_mult.insert(spell_id, mult);
255    }
256
257    /// Register NPC-filtered aura-382 attack/spell-power inheritance multipliers.
258    pub fn insert_pet_stat_inheritance_mult(
259        &mut self,
260        npc_id: u32,
261        attack_power_mult: f64,
262        spell_power_mult: f64,
263    ) {
264        self.inner
265            .passives
266            .pet_attack_power_inheritance
267            .insert(npc_id, attack_power_mult);
268        self.inner
269            .passives
270            .pet_spell_power_inheritance
271            .insert(npc_id, spell_power_mult);
272    }
273
274    /// Static crit-damage bonus (percent points) for a spell from passive spec/class auras.
275    pub fn insert_crit_damage_bonus(&mut self, spell_id: SpellIdx, bonus: f64) {
276        self.inner
277            .passives
278            .crit_damage_bonus
279            .insert(spell_id, bonus);
280    }
281
282    /// Player-wide crit damage scaled by the caster's own crit chance, filtered by a DBC school mask.
283    pub fn push_crit_damage_per_crit_chance(&mut self, school_mask: i32, percent: f64) {
284        self.inner
285            .passives
286            .crit_damage_per_crit_chance
287            .push((school_mask, percent));
288    }
289
290    /// One spell's crit bonus scaled by the caster's own crit chance (percent points of crit chance).
291    pub fn insert_crit_bonus_per_crit_chance(&mut self, spell_id: SpellIdx, percent: f64) {
292        self.inner
293            .passives
294            .crit_bonus_per_crit_chance
295            .insert(spell_id, percent);
296    }
297
298    #[must_use]
299    pub fn build(self) -> ResolvedGameData {
300        ResolvedGameData {
301            inner: Arc::new(self.inner),
302        }
303    }
304
305    effect_insert! {
306        effect i32;
307        insert_scaling_class => scaling_class,
308        insert_chain_targets => chain_targets,
309        insert_effect_type => effect_type,
310        insert_effect_mechanic => effect_mechanic,
311        insert_effect_attributes => effect_attributes,
312        insert_effect_aura => effect_aura,
313        insert_trigger_spell => trigger_spell,
314        insert_effect_misc_value_0 => misc_value_0,
315        insert_effect_misc_value_1 => misc_value_1,
316        insert_shapeshift_form_flags => shapeshift_form_flags,
317    }
318
319    effect_insert! {
320        parts i32;
321        insert_shapeshift_combat_round_time_ms => shapeshift_combat_round_time_ms,
322    }
323
324    effect_insert! {
325        parts f64;
326        insert_ap_coef      => ap_coef,
327        insert_sp_coef      => sp_coef,
328        insert_base_points  => base_points,
329        insert_amplitude    => amplitude,
330        insert_period       => period,
331        insert_coefficient  => coefficient,
332        insert_points_per_resource => points_per_resource,
333        insert_chain_multiplier => chain_multiplier,
334        insert_effect_radius => radius,
335    }
336
337    effect_insert! {
338        effect f64;
339        insert_power_coefficient_mastery_add => power_coefficient_mastery_add,
340    }
341}