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()
94        && effect_redirects_to_trigger_spell(effect)
95        && let Some(child) =
96            resolve_trigger_chain_payload(context.resolver, SpellId::new(effect.trigger_spell))
97                .await?
98    {
99        payload = child;
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        && child != 0
161        && !context.resolved.contains(&child)
162    {
163        context.pending.push_back(child);
164    }
165
166    context
167        .builder
168        .insert_ap_coef(spell_idx, effect_index, payload.ap_coef);
169    context
170        .builder
171        .insert_sp_coef(spell_idx, effect_index, payload.sp_coef);
172    context.builder.insert_power_coefficient_mastery_add(
173        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
174        payload.power_coefficient_mastery_add,
175    );
176    context
177        .builder
178        .insert_base_points(spell_idx, effect_index, payload.base_points);
179    context
180        .builder
181        .insert_amplitude(spell_idx, effect_index, payload.amplitude);
182    context.builder.insert_period(
183        spell_idx,
184        effect_index,
185        f64::from(wowlab_engine_domain::dbc::passive_period_ms(
186            passive_query,
187            effect.period,
188        )),
189    );
190    context
191        .builder
192        .insert_coefficient(spell_idx, effect_index, payload.coefficient);
193    context.builder.insert_scaling_class(
194        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
195        effect.scaling_class,
196    );
197    context.builder.insert_chain_targets(
198        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
199        wowlab_engine_domain::dbc::passive_chain_targets(passive_query, effect.chain_targets),
200    );
201    context.builder.insert_chain_multiplier(
202        spell_idx,
203        effect_index,
204        wowlab_engine_domain::dbc::passive_chain_multiplier(passive_query, effect.chain_multiplier),
205    );
206    context.builder.insert_effect_radius(
207        spell_idx,
208        effect_index,
209        wowlab_engine_domain::dbc::passive_radius(
210            passive_query,
211            f64::from(effect.radius_max.max(effect.radius_min)),
212        ),
213    );
214    context.builder.insert_effect_misc_value_0(
215        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
216        effect.misc_value_0,
217    );
218    context.builder.insert_effect_misc_value_1(
219        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
220        effect.misc_value_1,
221    );
222    context.builder.insert_shapeshift_form_flags(
223        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
224        effect.shapeshift_form_flags,
225    );
226    context.builder.insert_shapeshift_combat_round_time_ms(
227        spell_idx,
228        effect_index,
229        effect.shapeshift_combat_round_time_ms,
230    );
231    context.builder.insert_effect_type(
232        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
233        effect.effect,
234    );
235    context.builder.insert_effect_mechanic(
236        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
237        effect.mechanic,
238    );
239    context.builder.insert_effect_attributes(
240        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
241        effect.effect_attributes,
242    );
243    context.builder.insert_implicit_targets(
244        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
245        effect.implicit_target_a,
246        effect.implicit_target_b,
247    );
248    context.builder.insert_effect_aura(
249        wowlab_types::sim::EffectRef::new(spell_idx, effect_index),
250        effect.aura,
251    );
252    context.builder.insert_effect_class_mask(
253        spell_idx,
254        effect_index,
255        [
256            effect.effect_class_mask_1,
257            effect.effect_class_mask_2,
258            effect.effect_class_mask_3,
259            effect.effect_class_mask_4,
260        ],
261    );
262    context.builder.insert_points_per_resource(
263        spell_idx,
264        effect_index,
265        f64::from(effect.points_per_resource),
266    );
267}