Skip to main content

wowlab_engine_domain/rotation/context/
spell_usable.rs

1use wowlab_types::sim::{AuraProjectionKey, FastMap, IntMap, SpellIdx};
2
3use super::schema::SpellUsableOffsets;
4use crate::rotation::{
5    buffer::{BufferOffsets, ByteOffset},
6    resolver::{SpecResolver, SpellAuraPolicyKind},
7};
8
9pub(super) fn build_cooldown_bypass_offsets(
10    buffer_offsets: &BufferOffsets,
11    resolver: Option<&SpecResolver>,
12) -> FastMap<usize, usize> {
13    resolver
14        .into_iter()
15        .flat_map(SpecResolver::spell_aura_policies)
16        .filter_map(|policy| {
17            if policy.kind != SpellAuraPolicyKind::CooldownBypass {
18                return None;
19            }
20
21            let cooldown_offset = buffer_offsets
22                .slots
23                .cooldowns
24                .get(&policy.spell)
25                .map(ByteOffset::as_usize)?;
26            let aura_offset = buffer_offsets
27                .slots
28                .aura_projections
29                .get(&AuraProjectionKey::new(policy.aura, policy.on))
30                .map(ByteOffset::as_usize)?;
31
32            Some((cooldown_offset, aura_offset))
33        })
34        .collect()
35}
36
37pub(super) fn build_spell_usable_offsets(
38    buffer_offsets: &BufferOffsets,
39    resolver: Option<&SpecResolver>,
40    pending: &[(SpellIdx, usize)],
41) -> FastMap<usize, SpellUsableOffsets> {
42    let primary_resource_base = resolver
43        .and_then(SpecResolver::primary_resource_type)
44        .and_then(|resource| {
45            buffer_offsets
46                .slots
47                .resources
48                .get(&resource)
49                .map(ByteOffset::as_usize)
50        });
51    let secondary_resource_base = resolver
52        .and_then(SpecResolver::secondary_resource_type)
53        .and_then(|resource| {
54            buffer_offsets
55                .slots
56                .resources
57                .get(&resource)
58                .map(ByteOffset::as_usize)
59        });
60
61    let mut gating_by_spell: IntMap<SpellIdx, (usize, u8)> = IntMap::default();
62    let mut cost_bypass_by_spell: IntMap<SpellIdx, usize> = IntMap::default();
63    let mut cooldown_bypass_by_spell: IntMap<SpellIdx, usize> = IntMap::default();
64    let mut override_by_spell: IntMap<SpellIdx, (SpellIdx, usize, bool)> = IntMap::default();
65
66    for policy in resolver
67        .into_iter()
68        .flat_map(SpecResolver::spell_aura_policies)
69    {
70        let Some(aura_offset) = buffer_offsets
71            .slots
72            .aura_projections
73            .get(&AuraProjectionKey::new(policy.aura, policy.on))
74            .map(ByteOffset::as_usize)
75        else {
76            continue;
77        };
78
79        match policy.kind {
80            SpellAuraPolicyKind::Gate { min_stacks } => {
81                gating_by_spell.insert(policy.spell, (aura_offset, min_stacks));
82            }
83            SpellAuraPolicyKind::CostBypass => {
84                cost_bypass_by_spell.insert(policy.spell, aura_offset);
85            }
86            SpellAuraPolicyKind::CooldownBypass => {
87                cooldown_bypass_by_spell.insert(policy.spell, aura_offset);
88            }
89            SpellAuraPolicyKind::Override {
90                replacement,
91                shares_base_cooldown,
92            } => {
93                override_by_spell.insert(
94                    policy.spell,
95                    (replacement, aura_offset, shares_base_cooldown),
96                );
97            }
98        }
99    }
100
101    let mut offsets = FastMap::default();
102
103    for (spell_idx, field_offset) in pending {
104        let (gating_aura_offset, gating_aura_min_stacks) = gating_by_spell
105            .get(spell_idx)
106            .map_or((None, 0), |&(offset, stacks)| (Some(offset), stacks));
107        let (override_spell_offset, override_cooldown_base, override_aura_offset) =
108            match override_by_spell.get(spell_idx) {
109                Some((replacement, aura_offset, shares_base_cooldown)) => (
110                    buffer_offsets
111                        .slots
112                        .spells
113                        .get(replacement)
114                        .map(ByteOffset::as_usize),
115                    buffer_offsets
116                        .slots
117                        .cooldowns
118                        .get(if *shares_base_cooldown {
119                            spell_idx
120                        } else {
121                            replacement
122                        })
123                        .map(ByteOffset::as_usize),
124                    Some(*aura_offset),
125                ),
126                None => (None, None, None),
127            };
128
129        offsets.insert(
130            *field_offset,
131            SpellUsableOffsets {
132                resource_current: primary_resource_base,
133                secondary_resource_current: secondary_resource_base,
134                cooldown_base: buffer_offsets
135                    .slots
136                    .cooldowns
137                    .get(spell_idx)
138                    .map(ByteOffset::as_usize),
139                gating_aura_offset,
140                gating_aura_min_stacks,
141                cost_bypass_aura_offset: cost_bypass_by_spell.get(spell_idx).copied(),
142                cooldown_bypass_aura_offset: cooldown_bypass_by_spell.get(spell_idx).copied(),
143                override_spell_offset,
144                override_cooldown_base,
145                override_aura_offset,
146            },
147        );
148    }
149
150    offsets
151}