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::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
46        .builder
47        .insert_direct_damage_mult(spell_idx, passive_mods.direct);
48    context.builder.insert_direct_damage_flat(
49        spell_idx,
50        passive_mods.direct_flat,
51        passive_mods.direct_flat_mastery_coefficient,
52    );
53    context
54        .builder
55        .insert_periodic_damage_mult(spell_idx, passive_mods.periodic);
56    context
57        .builder
58        .insert_crit_chance_bonus(spell_idx, passive_mods.crit_chance);
59    context
60        .builder
61        .insert_crit_chance_mult(spell_idx, passive_mods.crit_chance_mult);
62    context
63        .builder
64        .insert_crit_damage_bonus(spell_idx, passive_mods.crit_damage_bonus);
65    context
66        .builder
67        .insert_crit_bonus_per_crit_chance(spell_idx, crit_bonus_per_crit_chance);
68    context
69        .builder
70        .insert_spell_class_flags(spell_idx, spell.spell_class_set, class_masks);
71
72    let resources = resolve_spell_resources(context, spell);
73    let mut props = spell_props_from(spell, resources);
74    let passive_query = PassiveQuery {
75        passives: context.passives,
76        spell,
77    };
78
79    apply_proc_properties(&mut props, context, spell, passive_query);
80    apply_resistance_properties(&mut props, passive_query);
81    apply_cost_properties(&mut props, context, spell);
82    apply_timing_and_targeting(&mut props, context, spell, passive_query);
83    context.builder.insert_spell_props(spell_idx, props);
84}
85
86fn apply_proc_properties(
87    props: &mut SpellProps,
88    context: &SpellResolutionContext<'_, '_>,
89    spell: &SpellDataFlat,
90    passive_query: PassiveQuery<'_>,
91) {
92    let proc_chance_mods =
93        wowlab_engine_domain::dbc::passive_proc_chance_mods(context.passives, spell);
94
95    props.proc_chance_pct = wowlab_types::numeric::f64_to_i32_saturating_round(
96        f64::from(props.proc_chance_pct) + proc_chance_mods.flat_pct,
97    );
98    props.proc_chance_multiplier_delta = proc_chance_mods.multiplier - 1.0;
99    props.rppm_base_rate *= proc_chance_mods.multiplier;
100    let proc_charges = wowlab_engine_domain::dbc::passive_modifier_components(
101        passive_query,
102        wowlab_engine_domain::dbc::ModifierPropertyKind::ProcCharges,
103    );
104
105    props.proc_charges = wowlab_types::numeric::f64_to_i32_saturating_round(
106        proc_charges.apply(f64::from(props.proc_charges)).max(0.0),
107    );
108    let proc_frequency = wowlab_engine_domain::dbc::passive_modifier_components(
109        passive_query,
110        wowlab_engine_domain::dbc::ModifierPropertyKind::ProcFrequency,
111    );
112
113    props.rppm_base_rate = proc_frequency.apply(props.rppm_base_rate).max(0.0);
114    let proc_cooldown = wowlab_engine_domain::dbc::passive_modifier_components(
115        passive_query,
116        wowlab_engine_domain::dbc::ModifierPropertyKind::ProcCooldown,
117    );
118
119    props.proc_category_recovery_ms = wowlab_types::numeric::f64_to_i32_saturating_round(
120        proc_cooldown
121            .apply(f64::from(props.proc_category_recovery_ms))
122            .max(0.0),
123    );
124}
125
126fn apply_resistance_properties(props: &mut SpellProps, passive_query: PassiveQuery<'_>) {
127    let hit_chance = wowlab_engine_domain::dbc::passive_modifier_components(
128        passive_query,
129        wowlab_engine_domain::dbc::ModifierPropertyKind::HitChance,
130    );
131
132    props.hit_chance_flat = hit_chance.flat;
133    props.hit_chance_multiplier = hit_chance.multiplier;
134    let target_resistance = wowlab_engine_domain::dbc::passive_modifier_components(
135        passive_query,
136        wowlab_engine_domain::dbc::ModifierPropertyKind::TargetResistance,
137    );
138
139    props.target_resistance_flat = target_resistance.flat;
140    props.target_resistance_multiplier = target_resistance.multiplier;
141    let dispel_resistance = wowlab_engine_domain::dbc::passive_modifier_components(
142        passive_query,
143        wowlab_engine_domain::dbc::ModifierPropertyKind::DispelResistance,
144    );
145
146    props.dispel_resistance_flat = dispel_resistance.flat;
147    props.dispel_resistance_multiplier = dispel_resistance.multiplier;
148}
149
150fn apply_cost_properties(
151    props: &mut SpellProps,
152    context: &SpellResolutionContext<'_, '_>,
153    spell: &SpellDataFlat,
154) {
155    props.cost_pct = wowlab_engine_domain::dbc::primary_cost_pct(
156        spell,
157        context.primary_resource_type,
158        context.active_aura_spell_ids,
159    );
160    props.max_cost_pct = wowlab_engine_domain::dbc::primary_max_cost_pct(
161        spell,
162        context.primary_resource_type,
163        context.active_aura_spell_ids,
164    );
165    props.optional_cost_pct = wowlab_engine_domain::dbc::primary_optional_cost_pct(
166        spell,
167        context.primary_resource_type,
168        context.active_aura_spell_ids,
169    );
170}
171
172fn apply_timing_and_targeting(
173    props: &mut SpellProps,
174    context: &SpellResolutionContext<'_, '_>,
175    spell: &SpellDataFlat,
176    passive_query: PassiveQuery<'_>,
177) {
178    props.max_charges =
179        wowlab_engine_domain::dbc::passive_max_charges(passive_query, props.max_charges);
180    wowlab_engine_domain::dbc::apply_passive_cooldowns(passive_query, props);
181
182    if is_channeled_spell(spell) {
183        props.cast_time_ms =
184            wowlab_engine_domain::dbc::passive_duration_ms(passive_query, props.cast_time_ms);
185    }
186
187    props.cast_time_ms =
188        wowlab_engine_domain::dbc::passive_cast_time_ms(passive_query, props.cast_time_ms);
189    props.gcd_ms = wowlab_engine_domain::dbc::passive_gcd_ms(passive_query, props.gcd_ms);
190    props.duration_ms =
191        wowlab_engine_domain::dbc::passive_duration_ms(passive_query, props.duration_ms);
192    props.max_stacks =
193        wowlab_engine_domain::dbc::passive_max_stacks(passive_query, props.max_stacks);
194    props.hostile_max_range = wowlab_engine_domain::dbc::passive_hostile_max_range(
195        passive_query,
196        props.hostile_max_range,
197    );
198    props.radius = wowlab_engine_domain::dbc::passive_radius(passive_query, props.radius);
199    props.chain_target_range = wowlab_engine_domain::dbc::passive_chain_target_range(
200        passive_query,
201        props.chain_target_range,
202    );
203    let max_affected_targets = wowlab_engine_domain::dbc::passive_max_targets(
204        passive_query,
205        i32::from(props.max_affected_targets),
206    )
207    .clamp(0, i32::from(u8::MAX));
208
209    props.max_affected_targets =
210        u8::try_from(max_affected_targets).expect("target count is clamped to u8");
211    props.cooldown_hasted =
212        wowlab_engine_domain::dbc::passive_hasted_cooldown(context.passives, spell);
213    props.gcd_haste_type = if wowlab_engine_domain::dbc::passive_hasted_gcd(context.passives, spell)
214    {
215        if matches!(
216            wowlab_engine_domain::dbc::DefenseType::try_from(spell.defense_type).ok(),
217            Some(
218                wowlab_engine_domain::dbc::DefenseType::Melee
219                    | wowlab_engine_domain::dbc::DefenseType::Ranged
220            )
221        ) {
222            wowlab_types::combat::GcdHasteType::AttackHaste
223        } else {
224            wowlab_types::combat::GcdHasteType::SpellCastSpeed
225        }
226    } else {
227        wowlab_types::combat::GcdHasteType::None
228    };
229}