Skip to main content

wowlab_engine_application/game_data/spells/
effects.rs

1use wowlab_engine_domain::dbc::{PassiveQuery, TriggerPayload, effect_redirects_to_trigger_spell};
2use wowlab_engine_ports::{DataResolver, EngineError, SpellId, TalentSelection};
3use wowlab_types::{
4    data::{SpellDataFlat, SpellEffect},
5    sim::SpellIdx,
6};
7
8use super::{
9    context::SpellResolutionContext,
10    descriptions::{described_effect_copy, resolve_trigger_chain_payload},
11};
12
13pub(super) async fn resolve_spell_effects(
14    context: &mut SpellResolutionContext<'_, '_>,
15    spell: &SpellDataFlat,
16    spell_idx: SpellIdx,
17    spell_id: SpellId,
18    spell_id_raw: u32,
19) -> Result<(), EngineError> {
20    let mut effects = context
21        .resolver
22        .get_spell_effects(spell_id)
23        .await
24        .map_err(|error| {
25            EngineError::spec_construction(format!(
26                "failed to resolve effects for spell {spell_id}: {error}"
27            ))
28        })?;
29
30    effects.sort_unstable_by_key(|effect| effect.index);
31
32    for mut effect in effects {
33        apply_runtime_talent_effect(context.talents, spell_id_raw, &mut effect);
34        let effect_index = runtime_effect_index(spell_id, &effect)?;
35        let payload = resolve_effect_payload(context, spell, &effect).await?;
36
37        insert_effect(context, spell, spell_idx, effect_index, &effect, &payload);
38    }
39
40    Ok(())
41}
42
43pub(crate) fn apply_runtime_talent_effect(
44    talents: &[TalentSelection],
45    spell_id_raw: u32,
46    effect: &mut SpellEffect,
47) {
48    let Some(talent) = talents
49        .iter()
50        .find(|talent| talent.spell_id == spell_id_raw)
51    else {
52        return;
53    };
54
55    if let Some(adjustment) = talent
56        .effect_overrides
57        .iter()
58        .find(|adjustment| adjustment.effect_index == effect.index)
59    {
60        effect.base_points = adjustment
61            .operation
62            .apply(effect.base_points, adjustment.value);
63    }
64}
65
66fn runtime_effect_index(spell_id: SpellId, effect: &SpellEffect) -> Result<u8, EngineError> {
67    let zero_based = u8::try_from(effect.index).map_err(|error| {
68        EngineError::spec_construction(format!(
69            "spell {spell_id} has invalid effect index {}: {error}",
70            effect.index
71        ))
72    })?;
73
74    zero_based.checked_add(1).ok_or_else(|| {
75        EngineError::spec_construction(format!(
76            "spell {spell_id} effect index {} exceeds the runtime index range",
77            effect.index
78        ))
79    })
80}
81
82pub(crate) async fn resolve_effect_payload(
83    context: &mut SpellResolutionContext<'_, '_>,
84    spell: &SpellDataFlat,
85    effect: &SpellEffect,
86) -> Result<TriggerPayload, EngineError> {
87    let mut payload = TriggerPayload::from_effect(effect);
88    let passive_query = PassiveQuery {
89        passives: context.passives,
90        spell,
91    };
92
93    if payload.has_no_coef() && effect_redirects_to_trigger_spell(effect) {
94        if let Some(child) =
95            resolve_trigger_chain_payload(context.resolver, SpellId::new(effect.trigger_spell))
96                .await?
97        {
98            payload = child;
99        }
100    }
101
102    payload.base_points = wowlab_engine_domain::dbc::modified_effect_base_points(
103        passive_query,
104        effect.index,
105        payload.base_points,
106    );
107    let declared = context
108        .effect_overrides
109        .iter()
110        .find(|((spell_id, index), _)| {
111            i64::from(*spell_id) == i64::from(spell.id) && i32::from(*index) - 1 == effect.index
112        });
113
114    if let Some((_, replacement)) = declared {
115        payload.base_points = *replacement;
116    } else if let Some(copied) = described_effect_copy(context, spell, effect).await? {
117        payload.base_points = copied;
118    }
119
120    payload.amplitude =
121        wowlab_engine_domain::dbc::modified_effect_amplitude(passive_query, payload.amplitude);
122    let power_coefficients = wowlab_engine_domain::dbc::modified_power_coefficients(
123        passive_query,
124        effect.index,
125        payload.sp_coef,
126        payload.ap_coef,
127    );
128
129    payload.sp_coef = power_coefficients.spell_power;
130    payload.ap_coef = power_coefficients.attack_power;
131    payload.power_coefficient_mastery_add = power_coefficients.mastery_add;
132    payload.coefficient = wowlab_engine_domain::dbc::modified_effect_coefficient(
133        passive_query,
134        effect.index,
135        payload.coefficient,
136    );
137
138    Ok(payload)
139}
140
141pub(crate) fn insert_effect(
142    context: &mut SpellResolutionContext<'_, '_>,
143    spell: &SpellDataFlat,
144    spell_idx: SpellIdx,
145    effect_index: u8,
146    effect: &SpellEffect,
147    payload: &TriggerPayload,
148) {
149    let passive_query = PassiveQuery {
150        passives: context.passives,
151        spell,
152    };
153
154    context.builder.insert_trigger_spell(
155        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
156        effect.trigger_spell,
157    );
158
159    if let Ok(child) = u32::try_from(effect.trigger_spell) {
160        if child != 0 && !context.resolved.contains(&child) {
161            context.pending.push_back(child);
162        }
163    }
164
165    context
166        .builder
167        .insert_ap_coef(spell_idx, effect_index, payload.ap_coef);
168    context
169        .builder
170        .insert_sp_coef(spell_idx, effect_index, payload.sp_coef);
171    context.builder.insert_power_coefficient_mastery_add(
172        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
173        payload.power_coefficient_mastery_add,
174    );
175    context
176        .builder
177        .insert_base_points(spell_idx, effect_index, payload.base_points);
178    context
179        .builder
180        .insert_amplitude(spell_idx, effect_index, payload.amplitude);
181    context.builder.insert_period(
182        spell_idx,
183        effect_index,
184        f64::from(wowlab_engine_domain::dbc::passive_period_ms(
185            passive_query,
186            effect.period,
187        )),
188    );
189    context
190        .builder
191        .insert_coefficient(spell_idx, effect_index, payload.coefficient);
192    context.builder.insert_scaling_class(
193        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
194        effect.scaling_class,
195    );
196    context.builder.insert_chain_targets(
197        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
198        wowlab_engine_domain::dbc::passive_chain_targets(passive_query, effect.chain_targets),
199    );
200    context.builder.insert_chain_multiplier(
201        spell_idx,
202        effect_index,
203        wowlab_engine_domain::dbc::passive_chain_multiplier(passive_query, effect.chain_multiplier),
204    );
205    context.builder.insert_effect_radius(
206        spell_idx,
207        effect_index,
208        wowlab_engine_domain::dbc::passive_radius(
209            passive_query,
210            f64::from(effect.radius_max.max(effect.radius_min)),
211        ),
212    );
213    context.builder.insert_effect_misc_value_0(
214        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
215        effect.misc_value_0,
216    );
217    context.builder.insert_effect_misc_value_1(
218        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
219        effect.misc_value_1,
220    );
221    context.builder.insert_shapeshift_form_flags(
222        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
223        effect.shapeshift_form_flags,
224    );
225    context.builder.insert_shapeshift_combat_round_time_ms(
226        spell_idx,
227        effect_index,
228        effect.shapeshift_combat_round_time_ms,
229    );
230    context.builder.insert_effect_type(
231        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
232        effect.effect,
233    );
234    context.builder.insert_effect_mechanic(
235        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
236        effect.mechanic,
237    );
238    context.builder.insert_effect_attributes(
239        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
240        effect.effect_attributes,
241    );
242    context.builder.insert_implicit_targets(
243        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
244        effect.implicit_target_a,
245        effect.implicit_target_b,
246    );
247    context.builder.insert_effect_aura(
248        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
249        effect.aura,
250    );
251    context.builder.insert_effect_class_mask(
252        spell_idx,
253        effect_index,
254        [
255            effect.effect_class_mask_1,
256            effect.effect_class_mask_2,
257            effect.effect_class_mask_3,
258            effect.effect_class_mask_4,
259        ],
260    );
261    context.builder.insert_points_per_resource(
262        spell_idx,
263        effect_index,
264        f64::from(effect.points_per_resource),
265    );
266}