wowlab_engine_combat/builder/
auto_attack_builder.rs1use 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#[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 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 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}