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()) {
36 continue;
37 }
38
39 let base_spell_indices: Vec<usize> = self
40 .definition
41 .spells
42 .iter()
43 .enumerate()
44 .filter_map(|(index, spell)| {
45 let directly_named = explicit_base > 0
46 && u32::try_from(explicit_base).ok() == Some(spell.spell_id);
47 let affect_listed = self.definition.game_data.effect_affects_spell(
48 SpellIdx::from_raw(aura_id),
49 effect_index,
50 SpellIdx::from_raw(spell.spell_id),
51 );
52
53 (directly_named || affect_listed).then_some(index)
54 })
55 .collect();
56
57 let Some(&template_index) = base_spell_indices.first() else {
58 continue;
59 };
60
61 self.ensure_replacement_spell(template_index, replacement);
62
63 if self.definition.pending_error.is_some()
64 || !self
65 .definition
66 .spells
67 .iter()
68 .any(|spell| spell.spell_id == replacement.as_u32())
69 {
70 continue;
71 }
72
73 let charges = self
74 .definition
75 .game_data
76 .proc_charges(SpellIdx::from_raw(aura_id))
77 .unwrap_or(0);
78 let persistent_form = charges == 0;
83
84 for index in base_spell_indices {
85 let spell = &mut self.definition.spells[index];
87
88 if spell.override_aura_id == 0 {
89 spell.override_aura_id = aura_id;
90 spell.override_spell_id = replacement.as_u32();
91 spell.override_shares_base_cooldown = persistent_form;
92 spell.override_preserves_aura = persistent_form;
93 }
94 }
95 }
96 }
97
98 fn is_unselected_talent_spell(&self, spell_id: u32) -> bool {
102 self.definition.talent_spell_ids.contains(&spell_id)
103 && !self
104 .definition
105 .selected_talent_spell_ids
106 .contains(&spell_id)
107 }
108
109 fn replacement_aura(
110 &mut self,
111 base_spell_index: usize,
112 replacement: SpellIdx,
113 ) -> Option<crate::state::LocalAuraIdx> {
114 let base_spell = &self.definition.spells[base_spell_index];
116 let base_aura_local = base_spell.applies_aura?;
117 let base_aura = &self.definition.aura_defs[base_aura_local.as_usize()];
119
120 if base_aura.aura_id != base_spell.spell_id {
121 return Some(base_aura_local);
122 }
123
124 if let Some(index) = self
125 .definition
126 .aura_defs
127 .iter()
128 .position(|aura| aura.aura_id == replacement.as_u32())
129 {
130 return Some(crate::state::LocalAuraIdx::new(
131 u8::try_from(index).expect("validated aura count fits in u8"),
132 ));
133 }
134
135 let has_applied_aura =
136 (1..=self.definition.game_data.max_effect_index(replacement)).any(|effect_index| {
137 wowlab_engine_domain::dbc::spell_effect_is(
138 self.definition
139 .game_data
140 .effect_type(replacement, effect_index),
141 wowlab_engine_domain::dbc::SpellEffectKind::ApplyAura,
142 )
143 });
144
145 if !has_applied_aura {
146 return Some(base_aura_local);
147 }
148
149 let name = format!("{} override", base_aura.name);
150 let draft = match AuraDefinitionDraft::new(&name, replacement.as_u32())
151 .apply_base_from_data(&self.definition.game_data, replacement.as_u32())
152 {
153 Ok(draft) => draft,
154 Err(error) => {
155 self.definition.pending_error = Some(error);
156
157 return None;
158 }
159 };
160 let draft = match base_aura.on {
161 AuraOn::Player => draft.on_player(),
162 AuraOn::Target => draft.on_target(),
163 AuraOn::Pet => draft.on_pet(),
164 };
165 let local = crate::state::LocalAuraIdx::new(
166 u8::try_from(self.definition.aura_defs.len()).expect("validated aura count fits in u8"),
167 );
168
169 self.definition.aura_ids.insert(name, replacement.as_u32());
170 self.definition.aura_defs.push(draft.finalize());
171
172 Some(local)
173 }
174
175 fn ensure_replacement_spell(&mut self, base_spell_index: usize, replacement: SpellIdx) {
176 if let Some(index) = self
177 .definition
178 .spells
179 .iter()
180 .position(|spell| spell.spell_id == replacement.as_u32())
181 {
182 if self.definition.cast_hooks[index].is_none() {
187 self.definition.cast_hooks[index] = self.definition.cast_hooks[base_spell_index];
189 }
190
191 return;
192 }
193
194 let replacement_aura = self.replacement_aura(base_spell_index, replacement);
195
196 if self.definition.pending_error.is_some() {
197 return;
198 }
199
200 let base_spell_id = self.definition.spells[base_spell_index].spell_id;
203 let mut spell = self.definition.spells[base_spell_index].clone();
205
206 spell.spell_id = replacement.as_u32();
207 spell.name = format!("{} override", spell.name);
208 spell.applies_aura = replacement_aura;
209 spell.override_aura_id = 0;
210 spell.override_spell_id = 0;
211 spell.override_aura_min_stacks = 0;
212 spell.override_shares_base_cooldown = false;
213 spell.override_preserves_aura = false;
214
215 if spell.damage_effect.spell_id == base_spell_id {
216 spell.damage_effect.spell_id = replacement.as_u32();
217 }
218
219 if spell.channel_tick_spell_id == base_spell_id {
220 spell.channel_tick_spell_id = replacement.as_u32();
221 }
222
223 let hook = self.definition.cast_hooks[base_spell_index];
226 let empower_release_hook = self.definition.empower_release_hooks[base_spell_index];
228 let tick_hook = self.definition.tick_hooks[base_spell_index];
230
231 self.definition.spells.push(spell);
232 self.definition.cast_hooks.push(hook);
233 self.definition
234 .empower_release_hooks
235 .push(empower_release_hook);
236 self.definition.tick_hooks.push(tick_hook);
237 }
238}