Skip to main content

wowlab_engine_application/game_data/spells/
properties.rs

1use wowlab_engine_domain::dbc::{PassiveQuery, is_channeled_spell, spell_props_from};
2use wowlab_engine_gamedata::{PassiveSpellModifiers, SpellProps};
3use wowlab_types::{data::SpellDataFlat, sim::SpellIdx};
4
5use super::{context::SpellResolutionContext, resources::resolve_spell_resources};
6
7pub(crate) fn insert_spell_properties(
8    context: &mut SpellResolutionContext<'_, '_>,
9    spell: &SpellDataFlat,
10    spell_idx: SpellIdx,
11) {
12    let labels: Vec<i32> = spell.labels.iter().map(|label| label.0).collect();
13    let class_masks = [
14        spell.spell_class_mask_1,
15        spell.spell_class_mask_2,
16        spell.spell_class_mask_3,
17        spell.spell_class_mask_4,
18    ];
19    let passive_mods = wowlab_engine_domain::dbc::passive_damage_mods(
20        context.passives,
21        spell.spell_class_set,
22        class_masks,
23        &labels,
24    );
25    let crit_bonus_per_crit_chance = wowlab_engine_domain::dbc::crit_chance_scaled_spell_percent(
26        context.crit_chance_scaled,
27        spell.spell_class_set,
28        class_masks,
29        &labels,
30    );
31
32    tracing::trace!(
33        spell_id = spell.id,
34        spell_name = %spell.name,
35        direct = passive_mods.direct,
36        direct_flat = passive_mods.direct_flat,
37        direct_flat_mastery_coefficient = passive_mods.direct_flat_mastery_coefficient,
38        periodic = passive_mods.periodic,
39        "static passive damage mult resolved"
40    );
41    context
42        .builder
43        .insert_school_mask(spell_idx, spell.school_mask);
44    context.builder.insert_spell_labels(spell_idx, labels);
45    context.builder.insert_passive_spell_modifiers(
46        spell_idx,
47        PassiveSpellModifiers {
48            direct_damage_mult: passive_mods.direct,
49            direct_damage_flat: passive_mods.direct_flat,
50            direct_damage_flat_mastery: passive_mods.direct_flat_mastery_coefficient,
51            periodic_damage_mult: passive_mods.periodic,
52            crit_chance_bonus: passive_mods.crit_chance,
53            crit_chance_mult: passive_mods.crit_chance_mult,
54            crit_damage_bonus: passive_mods.crit_damage_bonus,
55            crit_bonus_per_crit_chance,
56        },
57    );
58    context
59        .builder
60        .insert_spell_class_flags(spell_idx, spell.spell_class_set, class_masks);
61
62    let resources = resolve_spell_resources(context, spell);
63    let mut props = spell_props_from(spell, resources);
64    let passive_query = PassiveQuery {
65        passives: context.passives,
66        spell,
67    };
68
69    apply_proc_properties(&mut props, context, spell, passive_query);
70    apply_resistance_properties(&mut props, passive_query);
71    apply_cost_properties(&mut props, context, spell);
72    apply_timing_and_targeting(&mut props, context, spell, passive_query);
73    context.builder.insert_spell_props(spell_idx, props);
74}
75
76fn apply_proc_properties(
77    props: &mut SpellProps,
78    context: &SpellResolutionContext<'_, '_>,
79    spell: &SpellDataFlat,
80    passive_query: PassiveQuery<'_>,
81) {
82    let proc_chance_mods =
83        wowlab_engine_domain::dbc::passive_proc_chance_mods(context.passives, spell);
84
85    props.proc_chance_pct = wowlab_types::numeric::f64_to_i32_saturating_round(
86        f64::from(props.proc_chance_pct) + proc_chance_mods.flat_pct,
87    );
88    props.proc_chance_multiplier_delta = proc_chance_mods.multiplier - 1.0;
89    props.rppm_base_rate *= proc_chance_mods.multiplier;
90    let proc_charges = wowlab_engine_domain::dbc::passive_modifier_components(
91        passive_query,
92        wowlab_engine_domain::dbc::ModifierPropertyKind::ProcCharges,
93    );
94
95    props.proc_charges = wowlab_types::numeric::f64_to_i32_saturating_round(
96        proc_charges.apply(f64::from(props.proc_charges)).max(0.0),
97    );
98    let proc_frequency = wowlab_engine_domain::dbc::passive_modifier_components(
99        passive_query,
100        wowlab_engine_domain::dbc::ModifierPropertyKind::ProcFrequency,
101    );
102
103    props.rppm_base_rate = proc_frequency.apply(props.rppm_base_rate).max(0.0);
104    let proc_cooldown = wowlab_engine_domain::dbc::passive_modifier_components(
105        passive_query,
106        wowlab_engine_domain::dbc::ModifierPropertyKind::ProcCooldown,
107    );
108
109    props.proc_category_recovery_ms = wowlab_types::numeric::f64_to_i32_saturating_round(
110        proc_cooldown
111            .apply(f64::from(props.proc_category_recovery_ms))
112            .max(0.0),
113    );
114}
115
116fn apply_resistance_properties(props: &mut SpellProps, passive_query: PassiveQuery<'_>) {
117    let hit_chance = wowlab_engine_domain::dbc::passive_modifier_components(
118        passive_query,
119        wowlab_engine_domain::dbc::ModifierPropertyKind::HitChance,
120    );
121
122    props.hit_chance_flat = hit_chance.flat;
123    props.hit_chance_multiplier = hit_chance.multiplier;
124    let target_resistance = wowlab_engine_domain::dbc::passive_modifier_components(
125        passive_query,
126        wowlab_engine_domain::dbc::ModifierPropertyKind::TargetResistance,
127    );
128
129    props.target_resistance_flat = target_resistance.flat;
130    props.target_resistance_multiplier = target_resistance.multiplier;
131    let dispel_resistance = wowlab_engine_domain::dbc::passive_modifier_components(
132        passive_query,
133        wowlab_engine_domain::dbc::ModifierPropertyKind::DispelResistance,
134    );
135
136    props.dispel_resistance_flat = dispel_resistance.flat;
137    props.dispel_resistance_multiplier = dispel_resistance.multiplier;
138}
139
140fn apply_cost_properties(
141    props: &mut SpellProps,
142    context: &SpellResolutionContext<'_, '_>,
143    spell: &SpellDataFlat,
144) {
145    props.cost_pct = wowlab_engine_domain::dbc::primary_cost_pct(
146        spell,
147        context.primary_resource_type,
148        context.active_aura_spell_ids,
149    );
150    props.max_cost_pct = wowlab_engine_domain::dbc::primary_max_cost_pct(
151        spell,
152        context.primary_resource_type,
153        context.active_aura_spell_ids,
154    );
155    props.optional_cost_pct = wowlab_engine_domain::dbc::primary_optional_cost_pct(
156        spell,
157        context.primary_resource_type,
158        context.active_aura_spell_ids,
159    );
160}
161
162fn apply_timing_and_targeting(
163    props: &mut SpellProps,
164    context: &SpellResolutionContext<'_, '_>,
165    spell: &SpellDataFlat,
166    passive_query: PassiveQuery<'_>,
167) {
168    props.max_charges =
169        wowlab_engine_domain::dbc::passive_max_charges(passive_query, props.max_charges);
170    wowlab_engine_domain::dbc::apply_passive_cooldowns(passive_query, props);
171
172    if is_channeled_spell(spell) {
173        props.cast_time_ms =
174            wowlab_engine_domain::dbc::passive_duration_ms(passive_query, props.cast_time_ms);
175    }
176
177    props.cast_time_ms =
178        wowlab_engine_domain::dbc::passive_cast_time_ms(passive_query, props.cast_time_ms);
179    props.gcd_ms = wowlab_engine_domain::dbc::passive_gcd_ms(passive_query, props.gcd_ms);
180    props.duration_ms =
181        wowlab_engine_domain::dbc::passive_duration_ms(passive_query, props.duration_ms);
182    props.max_stacks =
183        wowlab_engine_domain::dbc::passive_max_stacks(passive_query, props.max_stacks);
184    props.hostile_max_range = wowlab_engine_domain::dbc::passive_hostile_max_range(
185        passive_query,
186        props.hostile_max_range,
187    );
188    props.radius = wowlab_engine_domain::dbc::passive_radius(passive_query, props.radius);
189    props.chain_target_range = wowlab_engine_domain::dbc::passive_chain_target_range(
190        passive_query,
191        props.chain_target_range,
192    );
193    let max_affected_targets = wowlab_engine_domain::dbc::passive_max_targets(
194        passive_query,
195        i32::from(props.max_affected_targets),
196    )
197    .clamp(0, i32::from(u8::MAX));
198
199    props.max_affected_targets =
200        u8::try_from(max_affected_targets).expect("target count is clamped to u8");
201    props.cooldown_hasted =
202        wowlab_engine_domain::dbc::passive_hasted_cooldown(context.passives, spell);
203    props.gcd_haste_type = if wowlab_engine_domain::dbc::passive_hasted_gcd(context.passives, spell)
204    {
205        if matches!(
206            wowlab_engine_domain::dbc::DefenseType::try_from(spell.defense_type).ok(),
207            Some(
208                wowlab_engine_domain::dbc::DefenseType::Melee
209                    | wowlab_engine_domain::dbc::DefenseType::Ranged
210            )
211        ) {
212            wowlab_types::combat::GcdHasteType::AttackHaste
213        } else {
214            wowlab_types::combat::GcdHasteType::SpellCastSpeed
215        }
216    } else {
217        wowlab_types::combat::GcdHasteType::None
218    };
219}