wowlab_engine_application/game_data/spells/
properties.rs1use 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}