Skip to main content

wowlab_engine_combat/systems/
spell_gates.rs

1#[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
17/// Set one independent availability gate and project the composed result.
18pub 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
86/// Rebuild active DBC availability restrictions and aura-state exceptions.
87pub(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;