wowlab_engine_combat/systems/
spell_gates.rs1#[cfg(test)]
2use wowlab_engine_domain::dbc::SpellAttributeKind;
3use wowlab_engine_domain::pool::{SpellGate, SpellGateUpdate};
4use wowlab_types::sim::SimTime;
5#[cfg(test)]
6use wowlab_types::sim::SpellIdx;
7
8use super::buffs::for_each_planned_actor_effect_damage_binding;
9use crate::state::{ActiveSpellGate, BuffEffect, CombatInstance, LocalSpellIdx};
10
11#[derive(Clone, Copy)]
12struct ActiveDbcGate {
13 source: crate::state::program::LocalDamageEffectIdx,
14 gate: ActiveSpellGate,
15}
16
17pub fn set_spell_gate(
19 program: &crate::state::CombatProgram,
20 state: &mut CombatInstance,
21 spell: LocalSpellIdx,
22 gate: SpellGate,
23 available: bool,
24 now: SimTime,
25) {
26 let handle = program.buffer_bindings.spell(spell);
27 let before = state.buffer.spell_at(handle).is_enabled;
28
29 set_spell_gate_at(program, state, spell, handle, gate, available);
30
31 #[cfg(feature = "rotation-optimizations")]
32 {
33 let after = state.buffer.spell_at(handle).is_enabled;
34
35 if after != before {
36 state.runtime.rotation_mutations.record_semantic(
37 crate::state::runtime::RotationSemanticMutation::AuraState,
38 if after != 0 {
39 crate::state::runtime::RotationMutationDirection::Rise
40 } else {
41 crate::state::runtime::RotationMutationDirection::Fall
42 },
43 now,
44 );
45 }
46 }
47
48 #[cfg(not(feature = "rotation-optimizations"))]
49 let _ = (before, now);
50}
51
52fn set_spell_gate_at(
53 program: &crate::state::CombatProgram,
54 state: &mut CombatInstance,
55 spell: LocalSpellIdx,
56 handle: wowlab_engine_domain::rotation::SpellSlotHandle,
57 gate: SpellGate,
58 available: bool,
59) {
60 let spell_idx = program.spell(spell).idx();
61 let base_enabled = state.buffer.spell_at(handle).is_enabled != 0;
62 let enabled = state.runtime.pools.spell_gates.set(
63 spell,
64 gate,
65 SpellGateUpdate {
66 base_enabled,
67 available,
68 },
69 );
70
71 let slot = state.buffer.spell_mut_at(handle);
72
73 if (slot.is_enabled != 0) != enabled {
74 tracing::trace!(
75 spell_id = spell_idx.0,
76 ?gate,
77 available,
78 enabled,
79 "SPELL_GATE_TRANSITION"
80 );
81 }
82
83 slot.is_enabled = i32::from(enabled);
84}
85
86pub(crate) fn refresh_spell_gates_for(
88 program: &crate::state::CombatProgram,
89 state: &mut CombatInstance,
90 bindings: &[crate::state::program::SpellConsumerBinding],
91) {
92 for &binding in bindings {
93 let local = binding.local;
94 let target = program.damage.spell_action(local);
95 let affected =
96 |rule: ActiveDbcGate| program.damage.effect_affects_action(rule.source, target);
97 let casting_exception = program.damage.action_at(target).casting_disabled_exception;
98 let mut restricted = false;
99 let mut ignores_aura_state = false;
100
101 for_each_planned_actor_effect_damage_binding(
102 crate::context::ActorView::new(program, state, wowlab_types::sim::ActorId::Player),
103 crate::state::EffectFamily::SPELL_GATE,
104 |_stacks, effect, binding| {
105 let BuffEffect::SpellGateFromEffect { gate, .. } = effect else {
106 return;
107 };
108 let rule = ActiveDbcGate {
109 source: binding
110 .source_effect
111 .expect("DBC spell gate carries a finalized source effect"),
112 gate: *gate,
113 };
114
115 restricted |= !casting_exception
116 && rule.gate == ActiveSpellGate::DisableAbilities
117 && !affected(rule);
118 ignores_aura_state |=
119 rule.gate == ActiveSpellGate::IgnoreAuraState && affected(rule);
120 },
121 );
122
123 state
124 .runtime
125 .pools
126 .spell_gates
127 .set_aura_state_exception(local, ignores_aura_state);
128 set_spell_gate_at(
129 program,
130 state,
131 local,
132 binding.handle,
133 SpellGate::ActiveAuraRestriction,
134 !restricted,
135 );
136 }
137}
138
139pub(crate) fn reset_spell_gates(program: &crate::state::CombatProgram, state: &mut CombatInstance) {
140 let (runtime, buffer) = (&mut state.runtime, &mut state.buffer);
141
142 for (spell, enabled) in runtime.pools.spell_gates.drain_reset() {
143 let handle = program.buffer_bindings.spell(spell);
144
145 buffer.spell_mut_at(handle).is_enabled = i32::from(enabled);
146 }
147}
148
149#[cfg(test)]
150#[path = "spell_gates/tests.rs"]
151mod tests;