Skip to main content

wowlab_engine_combat/builder/
auto_attack_builder.rs

1use wowlab_engine_macros::LowerData;
2
3use super::{
4    def::{DEFAULT_AA_AP_COEF, DEFAULT_SWING_MS},
5    lower::{BuilderToRuntimeCtx, InfoCtx},
6};
7use crate::state::{
8    CritChargeReduction, LocalSpellIdx, PetActionBar, SwingHookFn, SwingMissChanceHookFn,
9    SwingReplacementHookFn,
10};
11
12/// Mutable auto-attack definition lowered with the rest of the combat catalog.
13#[derive(Clone, Debug, LowerData)]
14#[must_use]
15#[expect(
16    clippy::struct_excessive_bools,
17    reason = "auto-attack behavior flags are independent combat coordinates"
18)]
19#[lower(
20    data = "crate::state::AutoAttackData",
21    ctx = "BuilderToRuntimeCtx<'_>",
22    info = "wowlab_types::game::AutoAttackInfo",
23    info_ctx = "InfoCtx<'_>",
24    info_fields(spell_id, swing_ms, ap_coef, is_pet)
25)]
26pub struct AutoAttackDefinitionDraft {
27    #[lower(copy)]
28    pub(crate) spell_id: u32,
29    #[lower(copy)]
30    pub(crate) swing_ms: u32,
31    #[lower(copy)]
32    pub(crate) ap_coef: f64,
33    #[lower(copy)]
34    pub(crate) is_physical: bool,
35    #[lower(copy)]
36    pub(crate) is_pet: bool,
37    #[lower(copy)]
38    pub(crate) npc_id: Option<u32>,
39    #[lower(copy)]
40    pub(crate) uses_spell_power: bool,
41    #[lower(copy)]
42    pub(crate) starts_active: bool,
43    #[lower(copy)]
44    pub(crate) on_crit_reduce_charge: Option<CritChargeReduction>,
45    #[lower(copy)]
46    pub(crate) miss_chance_hook: Option<SwingMissChanceHookFn>,
47    #[lower(copy)]
48    pub(crate) replacement_hook: Option<SwingReplacementHookFn>,
49    #[lower(copy)]
50    pub(crate) on_swing: Option<SwingHookFn>,
51    #[lower(copy)]
52    pub(crate) pet_action_bar: Option<PetActionBar>,
53}
54
55impl AutoAttackDefinitionDraft {
56    pub fn new() -> Self {
57        Self {
58            spell_id: 0,
59            swing_ms: DEFAULT_SWING_MS,
60            ap_coef: DEFAULT_AA_AP_COEF,
61            is_physical: true,
62            is_pet: false,
63            npc_id: None,
64            uses_spell_power: false,
65            starts_active: true,
66            on_crit_reduce_charge: None,
67            miss_chance_hook: None,
68            replacement_hook: None,
69            on_swing: None,
70            pet_action_bar: None,
71        }
72    }
73
74    pub fn spell_id(mut self, id: u32) -> Self {
75        self.spell_id = id;
76
77        self
78    }
79
80    pub fn swing_ms(mut self, ms: u32) -> Self {
81        self.swing_ms = ms;
82
83        self
84    }
85
86    pub fn ap_coef(mut self, coef: f64) -> Self {
87        self.ap_coef = coef;
88
89        self
90    }
91
92    pub fn is_pet(mut self) -> Self {
93        self.is_pet = true;
94        self.starts_active = true;
95
96        self
97    }
98
99    /// Derive whether this definition is a dormant action-driven attack from DBC effect 78.
100    pub fn apply_base_from_data(mut self, data: &wowlab_engine_gamedata::ResolvedGameData) -> Self {
101        let spell = wowlab_types::sim::SpellIdx::from_raw(self.spell_id);
102
103        self.starts_active = !(1..=data.max_effect_index(spell)).any(|effect_index| {
104            wowlab_engine_domain::dbc::spell_effect_is(
105                data.effect_type(spell, effect_index),
106                wowlab_engine_domain::dbc::SpellEffectKind::Attack,
107            )
108        });
109
110        self
111    }
112
113    /// Assign the creature identity used by NPC-filtered companion modifiers.
114    pub fn npc_id(mut self, npc_id: u32) -> Self {
115        self.npc_id = Some(npc_id);
116
117        self
118    }
119
120    pub fn uses_spell_power(mut self) -> Self {
121        self.uses_spell_power = true;
122
123        self
124    }
125
126    pub fn on_crit_reduce_charge(mut self, target: LocalSpellIdx, chance: f64) -> Self {
127        self.on_crit_reduce_charge = Some(CritChargeReduction { target, chance });
128
129        self
130    }
131
132    pub fn on_swing(mut self, hook: SwingHookFn) -> Self {
133        self.on_swing = Some(hook);
134
135        self
136    }
137
138    pub fn miss_chance_hook(mut self, hook: SwingMissChanceHookFn) -> Self {
139        self.miss_chance_hook = Some(hook);
140
141        self
142    }
143
144    pub fn replacement_hook(mut self, hook: SwingReplacementHookFn) -> Self {
145        self.replacement_hook = Some(hook);
146
147        self
148    }
149
150    pub fn pet_action_bar(mut self, action_bar: PetActionBar) -> Self {
151        self.pet_action_bar = Some(action_bar);
152
153        self
154    }
155}
156impl Default for AutoAttackDefinitionDraft {
157    fn default() -> Self {
158        Self::new()
159    }
160}