wowlab_engine_combat/builder/combat_builder/build/
overrides.rs1use super::*;
2
3pub(super) struct ActionOverrideRegistration<'a> {
4 pub(super) definition: &'a mut CombatSystemBuilder,
5}
6
7impl ActionOverrideRegistration<'_> {
8 pub(super) fn apply_dbc_action_overrides(&mut self) {
9 let mut registrations = Vec::with_capacity(self.definition.aura_defs.len());
10
11 for aura in &self.definition.aura_defs {
12 let aura_id = SpellIdx::from_raw(aura.aura_id);
13
14 for effect_index in 1..=self.definition.game_data.max_effect_index(aura_id) {
15 let Some(replacement) = self
16 .definition
17 .game_data
18 .override_action_spell(aura_id, effect_index)
19 else {
20 continue;
21 };
22
23 let explicit_base = self
24 .definition
25 .game_data
26 .effect_misc_value_0(aura_id, effect_index);
27
28 registrations.push((aura.aura_id, effect_index, replacement, explicit_base));
29 }
30 }
31
32 for (aura_id, effect_index, replacement, explicit_base) in registrations {
33 if self.is_unselected_talent_spell(replacement.as_u32()) {
37 continue;
38 }
39
40 let base_spell_indices: Vec<usize> = self
41 .definition
42 .spells
43 .iter()
44 .enumerate()
45 .filter_map(|(index, spell)| {
46 let directly_named = explicit_base > 0
47 && u32::try_from(explicit_base).ok() == Some(spell.spell_id);
48 let affect_listed = self.definition.game_data.effect_affects_spell(
49 SpellIdx::from_raw(aura_id),
50 effect_index,
51 SpellIdx::from_raw(spell.spell_id),
52 );
53
54 (directly_named || affect_listed).then_some(index)
55 })
56 .collect();
57
58 let Some(&template_index) = base_spell_indices.first() else {
59 continue;
60 };
61
62 self.ensure_replacement_spell(template_index, replacement);
63
64 if self.definition.pending_error.is_some()
65 || !self
66 .definition
67 .spells
68 .iter()
69 .any(|spell| spell.spell_id == replacement.as_u32())
70 {
71 continue;
72 }
73
74 let charges = self
75 .definition
76 .game_data
77 .proc_charges(SpellIdx::from_raw(aura_id))
78 .unwrap_or(0);
79 let persistent_form = charges == 0;
84
85 for index in base_spell_indices {
86 let spell = &mut self.definition.spells[index];
88
89 if spell.override_aura_id == 0 {
90 spell.override_aura_id = aura_id;
91 spell.override_spell_id = replacement.as_u32();
92 spell.override_shares_base_cooldown = persistent_form;
93 spell.override_preserves_aura = persistent_form;
94 }
95 }
96 }
97 }
98
99 fn is_unselected_talent_spell(&self, spell_id: u32) -> bool {
103 self.definition.talent_spell_ids.contains(&spell_id)
104 && !self
105 .definition
106 .selected_talent_spell_ids
107 .contains(&spell_id)
108 }
109
110 fn replacement_aura(
111 &mut self,
112 base_spell_index: usize,
113 replacement: SpellIdx,
114 ) -> Option<crate::state::LocalAuraIdx> {
115 let base_spell = &self.definition.spells[base_spell_index];
117 let base_aura_local = base_spell.applies_aura?;
118 let base_aura = &self.definition.aura_defs[base_aura_local.as_usize()];
120
121 if base_aura.aura_id != base_spell.spell_id {
122 return Some(base_aura_local);
123 }
124
125 if let Some(index) = self
126 .definition
127 .aura_defs
128 .iter()
129 .position(|aura| aura.aura_id == replacement.as_u32())
130 {
131 return Some(crate::state::LocalAuraIdx::new(
132 u8::try_from(index).expect("validated aura count fits in u8"),
133 ));
134 }
135
136 let has_applied_aura =
137 (1..=self.definition.game_data.max_effect_index(replacement)).any(|effect_index| {
138 wowlab_engine_domain::dbc::spell_effect_is(
139 self.definition
140 .game_data
141 .effect_type(replacement, effect_index),
142 wowlab_engine_domain::dbc::SpellEffectKind::ApplyAura,
143 )
144 });
145
146 if !has_applied_aura {
147 return Some(base_aura_local);
148 }
149
150 let name = format!("{} override", base_aura.name);
151 let draft = match AuraDefinitionDraft::new(&name, replacement.as_u32())
152 .apply_base_from_data(&self.definition.game_data, replacement.as_u32())
153 {
154 Ok(draft) => draft,
155 Err(error) => {
156 self.definition.pending_error = Some(error);
157
158 return None;
159 }
160 };
161 let draft = match base_aura.on {
162 AuraOn::Player => draft.on_player(),
163 AuraOn::Target => draft.on_target(),
164 AuraOn::Pet => draft.on_pet(),
165 };
166 let local = crate::state::LocalAuraIdx::new(
167 u8::try_from(self.definition.aura_defs.len()).expect("validated aura count fits in u8"),
168 );
169
170 self.definition.aura_ids.insert(name, replacement.as_u32());
171 self.definition.aura_defs.push(draft.finalize());
172
173 Some(local)
174 }
175
176 fn ensure_replacement_spell(&mut self, base_spell_index: usize, replacement: SpellIdx) {
177 if let Some(index) = self
178 .definition
179 .spells
180 .iter()
181 .position(|spell| spell.spell_id == replacement.as_u32())
182 {
183 if self.definition.cast_hooks[index].is_none() {
188 self.definition.cast_hooks[index] = self.definition.cast_hooks[base_spell_index];
190 }
191
192 return;
193 }
194
195 let replacement_aura = self.replacement_aura(base_spell_index, replacement);
196
197 if self.definition.pending_error.is_some() {
198 return;
199 }
200
201 let base_spell_id = self.definition.spells[base_spell_index].spell_id;
204 let mut spell = self.definition.spells[base_spell_index].clone();
206
207 spell.spell_id = replacement.as_u32();
208 spell.name = format!("{} override", spell.name);
209 spell.applies_aura = replacement_aura;
210 spell.override_aura_id = 0;
211 spell.override_spell_id = 0;
212 spell.override_aura_min_stacks = 0;
213 spell.override_shares_base_cooldown = false;
214 spell.override_preserves_aura = false;
215
216 if spell.damage_effect.spell_id == base_spell_id {
217 spell.damage_effect.spell_id = replacement.as_u32();
218 }
219
220 if spell.channel_tick_spell_id == base_spell_id {
221 spell.channel_tick_spell_id = replacement.as_u32();
222 }
223
224 let hook = self.definition.cast_hooks[base_spell_index];
227 let empower_release_hook = self.definition.empower_release_hooks[base_spell_index];
229 let tick_hook = self.definition.tick_hooks[base_spell_index];
231
232 self.definition.spells.push(spell);
233 self.definition.cast_hooks.push(hook);
234 self.definition
235 .empower_release_hooks
236 .push(empower_release_hook);
237 self.definition.tick_hooks.push(tick_hook);
238 }
239}