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