Skip to main content

wowlab_engine_combat/state/defs/
spell.rs

1use super::{
2    CdrEffect, CritChargeReduction, DEFAULT_GCD_MS, DamageEffectRef, HookCtx, LocalAuraIdx,
3    LocalSpellIdx, MAX_CDR_EFFECTS, PetActionBar, RuntimeDamageDef, SpellIdx, SwingHookFn,
4    SwingMissChanceHookFn, SwingReplacementHookFn,
5};
6use crate::state::AuraState;
7
8pub type CastHookFn = fn(&mut HookCtx<'_>);
9
10/// Content-owned condition for one DBC trigger edge's ordered effect program.
11pub(crate) type TriggerProgramPredicateFn = fn(&HookCtx<'_>) -> bool;
12
13/// Maximum number of active aura IDs that can prevent one aura application.
14pub const MAX_AURA_APPLICATION_EXCLUSIONS: usize = 5;
15
16/// Rank-dependent payload changes produced when an empower spell is released.
17///
18/// Content drivers provide only mechanics absent from `SpellEmpower` and stage data.
19#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
20#[must_use]
21pub struct EmpowerReleaseAdjustment {
22    pub(crate) periodic_tick_transfer: Option<EmpowerPeriodicTickTransfer>,
23}
24
25impl EmpowerReleaseAdjustment {
26    pub const fn new() -> Self {
27        Self {
28            periodic_tick_transfer: None,
29        }
30    }
31
32    /// Move selected periodic ticks into the initial hit and retain the remainder.
33    pub const fn transfer_periodic_ticks(total_ticks: u16, transferred_ticks: u16) -> Self {
34        Self {
35            periodic_tick_transfer: Some(EmpowerPeriodicTickTransfer {
36                total_ticks,
37                transferred_ticks,
38            }),
39        }
40    }
41}
42
43#[derive(Clone, Copy, Debug, Eq, PartialEq)]
44pub(crate) struct EmpowerPeriodicTickTransfer {
45    pub(crate) total_ticks: u16,
46    pub(crate) transferred_ticks: u16,
47}
48
49/// Driver invoked with the one-based release rank selected for an empower cast.
50pub(crate) type EmpowerReleaseHookFn = fn(&HookCtx<'_>, rank: u8) -> EmpowerReleaseAdjustment;
51
52/// Contiguous range into the combat definition's private `spell_effects` storage.
53#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
54pub struct SpellEffectRange {
55    pub start: u32,
56    pub len: u16,
57}
58
59#[derive(Clone, Copy, Debug, Eq, PartialEq)]
60#[non_exhaustive]
61pub enum ResourcePool {
62    Primary,
63    Secondary,
64}
65
66/// Ordered follow-up operation executed by a spell after its primary payload.
67#[derive(Clone, Copy, Debug)]
68#[non_exhaustive]
69pub enum SpellEffectData {
70    Conditional {
71        predicate: TriggerProgramPredicateFn,
72        effects: SpellEffectRange,
73    },
74    ApplyAura {
75        aura_local: LocalAuraIdx,
76    },
77    AddAuraStack {
78        aura_local: LocalAuraIdx,
79    },
80    RemoveAura {
81        aura_local: LocalAuraIdx,
82    },
83    InterruptCast {
84        lockout_ms: u32,
85    },
86    Dispel {
87        dispel_type: i32,
88        steal: bool,
89    },
90    Damage {
91        effect: DamageEffectRef,
92        damage: RuntimeDamageDef,
93        base_points: f64,
94        may_crit: bool,
95        attribute_flags: crate::DamageFlags,
96        requires_main_hand: bool,
97        requires_off_hand: bool,
98        equipped_item_requirement: Option<wowlab_types::data::EquippedItemRequirement>,
99    },
100    Heal {
101        effect: Option<DamageEffectRef>,
102        base: f64,
103        ap_coef: f64,
104        sp_coef: f64,
105        percent_of_max: f64,
106        may_crit: bool,
107    },
108    Energize {
109        effect: Option<DamageEffectRef>,
110        pool: ResourcePool,
111        amount: f64,
112    },
113    EnergizePercent {
114        resource_type: wowlab_types::combat::ResourceType,
115        percent: f64,
116        effect_index: u8,
117    },
118    ExtendAura {
119        aura_local: LocalAuraIdx,
120        amount_ms: u32,
121    },
122    MutateCooldown {
123        spell_local: LocalSpellIdx,
124        operation: wowlab_engine_domain::dbc::CooldownMutation,
125    },
126    Delayed {
127        delay_ms: u32,
128        profile_spell_id: u32,
129        effects: SpellEffectRange,
130    },
131}
132
133/// Resource costs and gains attached to a spell.
134#[derive(Clone, Copy, Debug)]
135#[must_use]
136pub struct SpellCost {
137    pub resource_cost: f64,
138    pub optional_resource_cost: f64,
139    pub resource_cost_pct: wowlab_types::data::ResourceCostPercent,
140    pub maximum_resource_cost_pct: wowlab_types::data::ResourceCostPercent,
141    pub optional_resource_cost_pct: wowlab_types::data::ResourceCostPercent,
142    pub resource_gain: f64,
143    pub secondary_resource_cost: f64,
144    pub secondary_optional_resource_cost: f64,
145    pub secondary_resource_gain: f64,
146    pub health_cost: f64,
147    pub health_cost_pct: f64,
148    pub health_max_cost_pct: f64,
149    pub health_optional_cost: f64,
150    pub health_optional_cost_pct: wowlab_types::data::ResourceCostPercent,
151}
152
153/// Cooldown, charge, category, and cooldown-reduction metadata.
154#[derive(Clone, Copy, Debug)]
155#[must_use]
156pub struct SpellCooldown {
157    pub cooldown_hasted: bool,
158    pub start_recovery_category: Option<wowlab_types::data::CooldownCategoryId>,
159    pub has_cooldown: bool,
160    pub cooldown_duration_ms: u32,
161    pub cooldown_category: Option<wowlab_types::data::CooldownCategoryId>,
162    pub category_cooldown_duration_ms: u32,
163    pub charge_category: Option<wowlab_types::data::CooldownCategoryId>,
164    pub max_charges: u8,
165    pub recharge_ms: u32,
166    pub dynamic_max_charges: bool,
167    pub cdr_effects: [CdrEffect; MAX_CDR_EFFECTS],
168    pub cdr_count: u8,
169}
170
171/// Channel timing, payload, and completion behavior.
172#[derive(Clone, Copy, Debug)]
173#[must_use]
174pub struct SpellChannel {
175    pub tick_count: u8,
176    pub tick_interval_ms: u32,
177    pub tick_damage: RuntimeDamageDef,
178    pub tick_spell_id: u32,
179    pub tick_damage_alt: RuntimeDamageDef,
180    pub tick_damage_alt_spell_id: u32,
181    pub tick_damage_aura: u32,
182    pub tick_cost: f64,
183    pub tick_cost_alt: f64,
184    pub tick_cost_aura: u32,
185    pub tick_effects: SpellEffectRange,
186    pub complete_effects: SpellEffectRange,
187    pub timing: SpellChannelTiming,
188    pub behavior: SpellChannelBehavior,
189    pub completion: SpellChannelCompletion,
190}
191
192#[derive(Clone, Copy, Debug)]
193#[must_use]
194pub struct SpellChannelTiming {
195    pub tick_zero: bool,
196    pub tick_is_periodic: bool,
197    pub hasted_ticks: bool,
198}
199
200#[derive(Clone, Copy, Debug)]
201#[must_use]
202pub struct SpellChannelBehavior {
203    pub is_channel: bool,
204    pub duration_hasted: bool,
205    pub tick_may_crit: bool,
206}
207
208#[derive(Clone, Copy, Debug)]
209#[must_use]
210pub struct SpellChannelCompletion {
211    pub tick_damage_alt_may_crit: bool,
212    pub gcd_on_start: bool,
213    pub apply_lag: bool,
214}
215
216/// Target selection and area-of-effect geometry.
217#[derive(Clone, Copy, Debug)]
218#[must_use]
219pub struct SpellTargeting {
220    pub explicit_target_mask: u32,
221    pub required_explicit_target_mask: u32,
222    pub aoe_max_targets: u8,
223    pub aoe_reduced_targets: u8,
224    pub aoe_full_targets: u8,
225    pub aoe_base_mult: f64,
226    pub chain_targeting: bool,
227    pub chain_multiplier: f64,
228    pub split: bool,
229    pub is_aoe: bool,
230}
231
232/// Cast-state, equipment, aura, and execute requirements.
233#[derive(Clone, Copy, Debug)]
234#[must_use]
235pub struct SpellRequirements {
236    pub shapeshift_required_mask: u64,
237    pub shapeshift_excluded_mask: u64,
238    pub caster_aura_spell: i32,
239    pub caster_aura_state: Option<AuraState>,
240    pub exclude_caster_aura_spell: i32,
241    pub exclude_caster_aura_state: Option<AuraState>,
242    pub target_aura_spell: i32,
243    pub target_aura_state: Option<AuraState>,
244    pub exclude_target_aura_spell: i32,
245    pub exclude_target_aura_state: Option<AuraState>,
246    pub equipped_item_requirement: Option<wowlab_types::data::EquippedItemRequirement>,
247    pub gating_aura_id: u32,
248    pub gating_aura_min_stacks: u8,
249    pub cost_bypass_aura_id: u32,
250    pub consume_aura_on_cast_id: u32,
251    pub cooldown_bypass_aura_id: u32,
252    pub execute_below_pct: f64,
253    pub instant_cast_aura_id: u32,
254    pub availability: SpellAvailability,
255    pub form: SpellFormRequirements,
256    pub equipment: SpellEquipmentRequirements,
257}
258
259#[derive(Clone, Copy, Debug)]
260#[must_use]
261pub struct SpellAvailability {
262    pub usable_while_casting: bool,
263    pub usable_while_moving: bool,
264    pub requires_unshifted: bool,
265}
266
267#[derive(Clone, Copy, Debug)]
268#[must_use]
269pub struct SpellFormRequirements {
270    pub allow_while_unshifted: bool,
271    pub requires_stealth: bool,
272}
273
274#[derive(Clone, Copy, Debug)]
275#[must_use]
276pub struct SpellEquipmentRequirements {
277    pub requires_behind_target: bool,
278    pub requires_main_hand: bool,
279    pub requires_off_hand: bool,
280}
281
282/// Aura-driven replacement behavior for one registered spell.
283#[derive(Clone, Copy, Debug)]
284#[must_use]
285pub struct SpellOverride {
286    pub aura_id: u32,
287    pub spell_id: u32,
288    pub aura_min_stacks: u8,
289    pub shares_base_cooldown: bool,
290    pub preserves_aura: bool,
291}
292
293/// Static definition of a castable spell.
294#[derive(Clone, Copy, Debug)]
295#[must_use]
296pub struct SpellData {
297    pub spell_id: u32,
298    pub name_idx: u16,
299    pub cast_time_ms: u32,
300    pub(crate) empower_cast_time_offset: u32,
301    pub empower_rank_count: u8,
302    pub max_empower_aura_id: u32,
303    pub gcd_haste_type: wowlab_types::combat::GcdHasteType,
304    pub gcd_ms: u32,
305    pub(crate) travel: crate::travel::SpellTravelData,
306    pub behavior: SpellBehavior,
307    pub cost: SpellCost,
308    pub damage: RuntimeDamageDef,
309    pub damage_policy: SpellDamagePolicy,
310    pub damage_attribute_flags: crate::DamageFlags,
311    pub damage_effect: DamageEffectRef,
312    pub base_points: f64,
313    pub applies_aura: Option<LocalAuraIdx>,
314    pub followup_effects: SpellEffectRange,
315    pub cooldown: SpellCooldown,
316    pub targeting: SpellTargeting,
317    pub channel: SpellChannel,
318    pub requirements: SpellRequirements,
319    pub overrides: SpellOverride,
320}
321
322#[derive(Clone, Copy, Debug)]
323#[must_use]
324pub struct SpellBehavior {
325    pub off_gcd: bool,
326    pub is_pet: bool,
327    pub breaks_stealth: bool,
328}
329
330#[derive(Clone, Copy, Debug)]
331#[must_use]
332pub struct SpellDamagePolicy {
333    pub may_crit: bool,
334    pub guaranteed_crit: bool,
335}
336impl SpellData {
337    /// Build a [`SpellIdx`] from this spell's game ID.
338    #[inline]
339    #[must_use]
340    pub fn idx(&self) -> SpellIdx {
341        SpellIdx(self.spell_id)
342    }
343
344    /// Whether this spell uses a charged cooldown (more than one charge, or a live aura can raise its category max charges at runtime).
345    #[inline]
346    #[must_use]
347    pub fn is_charged(&self) -> bool {
348        self.cooldown.max_charges > 1 || self.cooldown.dynamic_max_charges
349    }
350
351    /// Effective base GCD in milliseconds, falling back to [`DEFAULT_GCD_MS`] when zero.
352    #[inline]
353    #[must_use]
354    pub fn base_gcd_ms(&self) -> u32 {
355        if self.gcd_ms > 0 {
356            self.gcd_ms
357        } else {
358            DEFAULT_GCD_MS
359        }
360    }
361}
362
363#[derive(Clone, Copy, Debug)]
364#[expect(
365    clippy::struct_excessive_bools,
366    reason = "auto-attack behavior uses independent combat flags"
367)]
368// #t(rust_similar_structs) runtime auto-attack data is the immutable target of the builder lowering boundary
369pub struct AutoAttackData {
370    pub spell_id: u32,
371    pub swing_ms: u32,
372    pub ap_coef: f64,
373    pub is_physical: bool,
374    pub is_pet: bool,
375    pub npc_id: Option<u32>,
376    pub uses_spell_power: bool,
377    pub starts_active: bool,
378    pub on_crit_reduce_charge: Option<CritChargeReduction>,
379    pub miss_chance_hook: Option<SwingMissChanceHookFn>,
380    pub replacement_hook: Option<SwingReplacementHookFn>,
381    pub on_swing: Option<SwingHookFn>,
382    pub pet_action_bar: Option<PetActionBar>,
383}