Skip to main content

wowlab_engine_combat/pipeline/
empower.rs

1use super::{
2    CombatCtx, EmpowerReleaseAdjustment, HookCtx, LocalSpellIdx, RuntimeDamageDef,
3    SpellCastPayload, SpellData, aura_stacks_by_id, secondary_spends_all_points,
4};
5
6pub(super) fn empower_release_adjustment(
7    ctx: &mut CombatCtx<'_>,
8    spell_local: LocalSpellIdx,
9    rank: u8,
10) -> EmpowerReleaseAdjustment {
11    if rank == 0 {
12        return EmpowerReleaseAdjustment::default();
13    }
14
15    let hook = ctx
16        .state
17        .defs
18        .empower_release_hooks
19        .get(spell_local.as_usize())
20        .copied()
21        .flatten();
22    let Some(hook) = hook else {
23        return EmpowerReleaseAdjustment::default();
24    };
25    let hook_ctx = HookCtx::new(
26        crate::context::HookCtxServices {
27            state: ctx.state,
28            buf: ctx.buf,
29            sink: ctx.sink,
30            rng: ctx.rng,
31        },
32        crate::context::HookCtxRequest::for_target(ctx.now, ctx.target).with_source(ctx.source),
33    )
34    .with_empower_rank(rank);
35
36    hook(&hook_ctx, rank)
37}
38
39pub(super) fn adjust_empower_payload(
40    state: &crate::state::CombatState,
41    spell: &SpellData,
42    damage: RuntimeDamageDef,
43    adjustment: EmpowerReleaseAdjustment,
44) -> SpellCastPayload {
45    let unchanged = SpellCastPayload {
46        damage,
47        base_points: spell.base_points,
48        aura_duration_ms: None,
49    };
50    let Some(transfer) = adjustment.periodic_tick_transfer else {
51        return unchanged;
52    };
53
54    let Some((periodic, aura_duration_ms)) = empower_periodic_transfer_data(
55        state,
56        spell,
57        transfer.total_ticks,
58        transfer.transferred_ticks,
59    ) else {
60        return unchanged;
61    };
62    let ticks = f64::from(transfer.transferred_ticks);
63
64    match (damage, periodic) {
65        (
66            RuntimeDamageDef::SpCoefficient { coef, is_physical },
67            crate::state::PeriodicKind::DamageSp {
68                coef: periodic_coef,
69                is_physical: periodic_physical,
70            }
71            | crate::state::PeriodicKind::RollingDamageSp {
72                coef: periodic_coef,
73                is_physical: periodic_physical,
74            },
75        ) if is_physical == periodic_physical => SpellCastPayload {
76            damage: RuntimeDamageDef::SpCoefficient {
77                coef: coef + periodic_coef * ticks,
78                is_physical,
79            },
80            base_points: spell.base_points,
81            aura_duration_ms: Some(aura_duration_ms),
82        },
83        (
84            RuntimeDamageDef::None,
85            crate::state::PeriodicKind::DamageSp { coef, is_physical }
86            | crate::state::PeriodicKind::RollingDamageSp { coef, is_physical },
87        ) => SpellCastPayload {
88            damage: RuntimeDamageDef::SpCoefficient {
89                coef: coef * ticks,
90                is_physical,
91            },
92            base_points: spell.base_points,
93            aura_duration_ms: Some(aura_duration_ms),
94        },
95        (
96            RuntimeDamageDef::ApCoefficient {
97                coef,
98                is_physical,
99                ap_type,
100            },
101            crate::state::PeriodicKind::DamageAp {
102                coef: periodic_coef,
103                is_physical: periodic_physical,
104            }
105            | crate::state::PeriodicKind::RollingDamageAp {
106                coef: periodic_coef,
107                is_physical: periodic_physical,
108            },
109        ) if is_physical == periodic_physical => SpellCastPayload {
110            damage: RuntimeDamageDef::ApCoefficient {
111                coef: coef + periodic_coef * ticks,
112                is_physical,
113                ap_type,
114            },
115            base_points: spell.base_points,
116            aura_duration_ms: Some(aura_duration_ms),
117        },
118        (
119            RuntimeDamageDef::None,
120            crate::state::PeriodicKind::DamageAp { coef, is_physical }
121            | crate::state::PeriodicKind::RollingDamageAp { coef, is_physical },
122        ) => SpellCastPayload {
123            damage: RuntimeDamageDef::ApCoefficient {
124                coef: coef * ticks,
125                is_physical,
126                ap_type: crate::state::WeaponApType::None,
127            },
128            base_points: spell.base_points,
129            aura_duration_ms: Some(aura_duration_ms),
130        },
131        (
132            RuntimeDamageDef::Flat(amount),
133            crate::state::PeriodicKind::FlatDamage {
134                amount: periodic_amount,
135                ..
136            }
137            | crate::state::PeriodicKind::RollingFlatDamage {
138                amount: periodic_amount,
139                ..
140            },
141        ) => SpellCastPayload {
142            damage: RuntimeDamageDef::Flat(amount + periodic_amount * ticks),
143            base_points: spell.base_points,
144            aura_duration_ms: Some(aura_duration_ms),
145        },
146        (
147            RuntimeDamageDef::None,
148            crate::state::PeriodicKind::FlatDamage { amount, .. }
149            | crate::state::PeriodicKind::RollingFlatDamage { amount, .. },
150        ) => SpellCastPayload {
151            damage: RuntimeDamageDef::Flat(amount * ticks),
152            base_points: spell.base_points,
153            aura_duration_ms: Some(aura_duration_ms),
154        },
155        (
156            direct @ (RuntimeDamageDef::SpCoefficient { .. }
157            | RuntimeDamageDef::ApCoefficient { .. }),
158            crate::state::PeriodicKind::FlatDamage { amount, .. }
159            | crate::state::PeriodicKind::RollingFlatDamage { amount, .. },
160        ) => SpellCastPayload {
161            damage: direct,
162            base_points: spell.base_points + amount * ticks,
163            aura_duration_ms: Some(aura_duration_ms),
164        },
165        _ => {
166            tracing::warn!(
167                spell_id = spell.spell_id,
168                "empower periodic transfer has incompatible direct and periodic damage kinds"
169            );
170
171            unchanged
172        }
173    }
174}
175
176pub(super) fn empower_periodic_transfer_data(
177    state: &crate::state::CombatState,
178    spell: &SpellData,
179    total_ticks: u16,
180    transferred_ticks: u16,
181) -> Option<(crate::state::PeriodicKind, u32)> {
182    if transferred_ticks > total_ticks {
183        tracing::warn!(
184            spell_id = spell.spell_id,
185            total_ticks,
186            transferred_ticks,
187            "empower release driver transferred more periodic ticks than exist"
188        );
189
190        return None;
191    }
192
193    let Some(aura_local) = spell.applies_aura else {
194        tracing::warn!(
195            spell_id = spell.spell_id,
196            "empower periodic transfer requires an applied aura"
197        );
198
199        return None;
200    };
201    let Some(aura) = state.defs.auras.get(aura_local.as_usize()).copied() else {
202        tracing::warn!(
203            spell_id = spell.spell_id,
204            "empower periodic transfer references an unknown aura"
205        );
206
207        return None;
208    };
209    let Some(periodic) = aura.periodic else {
210        tracing::warn!(
211            spell_id = spell.spell_id,
212            aura_id = aura.aura_id,
213            "empower periodic transfer requires a periodic aura"
214        );
215
216        return None;
217    };
218    let remaining_ticks = total_ticks - transferred_ticks;
219    let Some(aura_duration_ms) = u32::from(remaining_ticks).checked_mul(periodic.tick_interval_ms)
220    else {
221        tracing::warn!(
222            spell_id = spell.spell_id,
223            aura_id = aura.aura_id,
224            remaining_ticks,
225            tick_interval_ms = periodic.tick_interval_ms,
226            "empower periodic duration overflow"
227        );
228
229        return None;
230    };
231
232    Some((periodic.effect, aura_duration_ms))
233}
234
235pub(super) fn finisher_scaled_damage(ctx: &CombatCtx<'_>, spell: &SpellData) -> RuntimeDamageDef {
236    if spell.cost.secondary_resource_cost > 0.0 && secondary_spends_all_points(ctx.state) {
237        spell
238            .damage
239            .scaled(ctx.state.runtime.resources.last_secondary_spent)
240    } else {
241        spell.damage
242    }
243}
244
245pub(super) fn empower_rank_for_cast(
246    ctx: &CombatCtx<'_>,
247    spell_local: LocalSpellIdx,
248    requested_rank: u8,
249) -> u8 {
250    let Some(spell) = ctx.state.defs.spells.get(spell_local.as_usize()).copied() else {
251        return 0;
252    };
253
254    if spell.empower_rank_count > 0
255        && spell.max_empower_aura_id != 0
256        && aura_stacks_by_id(
257            ctx.state,
258            ctx.buf,
259            spell.max_empower_aura_id,
260            ctx.source,
261            Some(ctx.target),
262        ) > 0
263    {
264        spell.empower_rank_count
265    } else if spell.empower_rank_count > 0 {
266        requested_rank.max(1).min(spell.empower_rank_count)
267    } else {
268        0
269    }
270}