wowlab_engine_combat/systems/
spell_gates.rs1use wowlab_engine_domain::{
2 dbc::{
3 ResolvedGameDataSemanticExt as _, SpellAttributeKind, SpellEffectKind, spell_attribute_is,
4 spell_effect_is,
5 },
6 pool::{SpellGate, SpellGateUpdate},
7 rotation::DenseBuffer,
8};
9use wowlab_types::sim::SpellIdx;
10
11use super::buffs::for_each_active_aura_effect;
12use crate::state::{ActiveSpellGate, BuffEffect, CombatState, LocalSpellIdx};
13
14#[derive(Clone, Copy)]
15struct ActiveDbcGate {
16 source_spell_id: u32,
17 effect_index: u8,
18 gate: ActiveSpellGate,
19}
20
21pub fn set_spell_gate(
23 state: &mut CombatState,
24 buf: &mut DenseBuffer,
25 spell: LocalSpellIdx,
26 gate: SpellGate,
27 available: bool,
28) {
29 let spell_idx = state.spell(spell).idx();
30 let Some(base_enabled) = buf.spell(spell_idx).map(|slot| slot.is_enabled != 0) else {
31 return;
32 };
33 let enabled = state.runtime.pools.spell_gates.set(
34 spell,
35 gate,
36 SpellGateUpdate {
37 base_enabled,
38 available,
39 },
40 );
41
42 if let Some(slot) = buf.spell_mut(spell_idx) {
43 if (slot.is_enabled != 0) != enabled {
44 tracing::trace!(
45 spell_id = spell_idx.0,
46 ?gate,
47 available,
48 enabled,
49 "SPELL_GATE_TRANSITION"
50 );
51 }
52
53 slot.is_enabled = i32::from(enabled);
54 }
55}
56
57pub(crate) fn synchronize_active_spell_gates(state: &mut CombatState, buf: &mut DenseBuffer) {
59 let mut active = Vec::new();
60
61 for_each_active_aura_effect(state, buf, |_stacks, effect| {
62 if let BuffEffect::SpellGateFromEffect {
63 source_spell_id,
64 effect_index,
65 gate,
66 } = effect
67 {
68 active.push(ActiveDbcGate {
69 source_spell_id: *source_spell_id,
70 effect_index: *effect_index,
71 gate: *gate,
72 });
73 }
74 });
75
76 for raw_local in 0..state.defs.spells.len() {
77 let Some(local) = u8::try_from(raw_local).ok().map(LocalSpellIdx) else {
78 continue;
79 };
80 let Some(spell) = state.defs.spells.get(raw_local) else {
81 continue;
82 };
83 let spell_id = spell.spell_id;
84 let affected = |rule: &ActiveDbcGate| {
85 state.config.game_data.effect_affects_spell(
86 SpellIdx::from_raw(rule.source_spell_id),
87 rule.effect_index,
88 SpellIdx::from_raw(spell_id),
89 )
90 };
91 let spell_idx = SpellIdx::from_raw(spell_id);
92 let casting_exception = state
93 .config
94 .game_data
95 .spell_attributes(spell_idx)
96 .is_some_and(|attributes| {
97 spell_attribute_is(attributes, SpellAttributeKind::RangedAbility)
98 || spell_attribute_is(attributes, SpellAttributeKind::IgnoreCastingDisabled)
99 })
100 || (1..=state.config.game_data.max_effect_index(spell_idx)).any(|effect_index| {
101 spell_effect_is(
102 state.config.game_data.effect_type(spell_idx, effect_index),
103 SpellEffectKind::Attack,
104 )
105 });
106 let restricted = !casting_exception
107 && active
108 .iter()
109 .any(|rule| rule.gate == ActiveSpellGate::DisableAbilities && !affected(rule));
110 let ignores_aura_state = active
111 .iter()
112 .any(|rule| rule.gate == ActiveSpellGate::IgnoreAuraState && affected(rule));
113
114 state
115 .runtime
116 .pools
117 .spell_gates
118 .set_aura_state_exception(local, ignores_aura_state);
119 set_spell_gate(
120 state,
121 buf,
122 local,
123 SpellGate::ActiveAuraRestriction,
124 !restricted,
125 );
126 }
127}
128
129pub(crate) fn ignores_aura_state(state: &CombatState, spell_id: u32) -> bool {
130 state
131 .spell_local(spell_id)
132 .is_some_and(|local| state.runtime.pools.spell_gates.ignores_aura_state(&local))
133}
134
135pub(crate) fn reset_spell_gates(state: &mut CombatState, buf: &mut DenseBuffer) {
136 for (spell, enabled) in state.runtime.pools.spell_gates.reset() {
137 let spell_idx = state.spell(spell).idx();
138
139 if let Some(slot) = buf.spell_mut(spell_idx) {
140 slot.is_enabled = i32::from(enabled);
141 }
142 }
143}
144
145#[cfg(test)]
146#[path = "spell_gates/tests.rs"]
147mod tests;