Skip to main content

wowlab_engine_domain/dbc/semantics/
resolved_game_data.rs

1//! Typed semantic interpretation layered over neutral resolved-data accessors.
2
3use wowlab_engine_gamedata::ResolvedGameData;
4use wowlab_types::sim::SpellIdx;
5
6use super::{
7    AuraState, AuraSubtypeKind, DispelType, Mechanic, MechanicMask, SpellSchoolMask,
8    aura_subtype_kind,
9};
10
11fn infer_granted_aura_state(data: &ResolvedGameData, spell: SpellIdx) -> Option<AuraState> {
12    let mechanics = data.all_effect_mechanic_mask(spell);
13
14    if DispelType::try_from(data.dispel_type(spell)).ok() == Some(DispelType::Enrage) {
15        return Some(AuraState::Enraged);
16    }
17
18    if mechanics.contains(MechanicMask::BLEED) {
19        return Some(AuraState::Bleeding);
20    }
21
22    if mechanics.contains(MechanicMask::BANISH) {
23        return Some(AuraState::Banished);
24    }
25
26    let frost = data
27        .school_mask(spell)
28        .is_some_and(|raw| SpellSchoolMask::from_dbc(raw).contains(SpellSchoolMask::FROST));
29    let immobilizes = (1..=data.max_effect_index(spell)).any(|effect_index| {
30        matches!(
31            aura_subtype_kind(data.effect_aura(spell, effect_index)),
32            Some(
33                AuraSubtypeKind::Stun
34                    | AuraSubtypeKind::Root
35                    | AuraSubtypeKind::RootRespectsThreatTable
36            )
37        )
38    });
39
40    (frost && immobilizes).then_some(AuraState::Frozen)
41}
42
43pub trait ResolvedGameDataSemanticExt {
44    /// Replacement action encoded by `Override Action Spell`.
45    fn override_action_spell(&self, aura_spell_id: SpellIdx, effect_index: u8) -> Option<SpellIdx>;
46
47    /// Whether a modifier effect's typed label/global/class-family policy includes `target`.
48    fn effect_affects_spell(&self, source: SpellIdx, effect_index: u8, target: SpellIdx) -> bool;
49
50    /// Spell-level mechanic mask from `SpellCategories.Mechanic`.
51    fn spell_mechanic_mask(&self, spell: SpellIdx) -> MechanicMask;
52
53    /// Aggregate spell- and effect-level mechanic mask for one executed effect.
54    fn effect_mechanic_mask(&self, spell: SpellIdx, effect_index: u8) -> MechanicMask;
55
56    /// Aggregate mechanic mask across the spell and all populated effects.
57    fn all_effect_mechanic_mask(&self, spell: SpellIdx) -> MechanicMask;
58
59    /// Aura state generically granted by this DBC spell, when derivable without a spell catalog.
60    fn granted_aura_state(&self, spell: SpellIdx) -> Option<AuraState>;
61}
62
63impl ResolvedGameDataSemanticExt for ResolvedGameData {
64    fn override_action_spell(&self, aura_spell_id: SpellIdx, effect_index: u8) -> Option<SpellIdx> {
65        (aura_subtype_kind(self.effect_aura(aura_spell_id, effect_index))
66            == Some(AuraSubtypeKind::OverrideActionSpell))
67        .then(|| self.effect_base_points_spell(aura_spell_id, effect_index))
68        .flatten()
69    }
70
71    fn effect_affects_spell(&self, source: SpellIdx, effect_index: u8, target: SpellIdx) -> bool {
72        let aura = aura_subtype_kind(self.effect_aura(source, effect_index));
73        let label = match aura {
74            Some(
75                AuraSubtypeKind::AddPercentLabelModifier | AuraSubtypeKind::AddFlatLabelModifier,
76            ) => self.effect_misc_value_1(source, effect_index),
77            Some(
78                AuraSubtypeKind::ModDamageTakenFromCasterSpellsLabel
79                | AuraSubtypeKind::ModTimeRateLabel,
80            ) => self.effect_misc_value_0(source, effect_index),
81            Some(AuraSubtypeKind::ModTimeRate) => return true,
82            _ => 0,
83        };
84
85        (label != 0 && self.has_label(target, label))
86            || self.effect_class_mask_affects_spell(source, effect_index, target)
87    }
88
89    fn spell_mechanic_mask(&self, spell: SpellIdx) -> MechanicMask {
90        Mechanic::from_dbc(self.spell_mechanic(spell)).map_or(MechanicMask::empty(), Mechanic::mask)
91    }
92
93    fn effect_mechanic_mask(&self, spell: SpellIdx, effect_index: u8) -> MechanicMask {
94        self.spell_mechanic_mask(spell)
95            | Mechanic::from_dbc(self.effect_mechanic(spell, effect_index))
96                .map_or(MechanicMask::empty(), Mechanic::mask)
97    }
98
99    fn all_effect_mechanic_mask(&self, spell: SpellIdx) -> MechanicMask {
100        (1..=self.max_effect_index(spell)).fold(
101            self.spell_mechanic_mask(spell),
102            |mechanics, effect_index| mechanics | self.effect_mechanic_mask(spell, effect_index),
103        )
104    }
105
106    fn granted_aura_state(&self, spell: SpellIdx) -> Option<AuraState> {
107        infer_granted_aura_state(self, spell)
108    }
109}