Skip to main content

wowlab_engine_domain/dbc/semantics/
spell_effect_targets.rs

1//! Target requirements intrinsic to each DBC spell-effect kind.
2
3use super::ImplicitTargetObject;
4
5/// How a spell effect obtains its target when its A/B selectors do not provide one.
6#[derive(Clone, Copy, Debug, Eq, PartialEq)]
7#[repr(u8)]
8#[non_exhaustive]
9pub enum SpellEffectImplicitTarget {
10    None,
11    Explicit,
12    Caster,
13}
14
15/// Target fallback metadata for one DBC spell-effect kind.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct SpellEffectTargetSemantic {
18    pub implicit: SpellEffectImplicitTarget,
19    pub object: ImplicitTargetObject,
20}
21
22const fn effect_target(
23    implicit: SpellEffectImplicitTarget,
24    object: ImplicitTargetObject,
25) -> SpellEffectTargetSemantic {
26    SpellEffectTargetSemantic { implicit, object }
27}
28
29use ImplicitTargetObject as O;
30use SpellEffectImplicitTarget as I;
31
32/// `TrinityCore` `SpellEffectInfo::_data` lookup for effect IDs 0 through 355.
33#[rustfmt::skip]
34#[must_use]
35pub fn spell_effect_target_semantic(raw: i32) -> Option<SpellEffectTargetSemantic> {
36    let semantic = match raw {
37        166 | 169 | 181 | 192 | 267 | 315 => effect_target(I::Caster, O::Unit),
38        18 | 172 => effect_target(I::Explicit, O::CorpseAlly),
39        116 => effect_target(I::Explicit, O::CorpseEnemy),
40        27..=28 | 50 | 56 | 72 | 76 | 104 | 106 | 109 | 135 | 149 | 188 => effect_target(I::Explicit, O::Destination),
41        86..=89 => effect_target(I::Explicit, O::GameObject),
42        33 => effect_target(I::Explicit, O::GameObjectItem),
43        5 | 53..=54 | 99 | 101 | 127 | 156 | 158 | 163 | 206 | 223 | 243 | 258..=259 | 261 | 263..=264 | 266 | 269 | 273 | 295..=297 | 301 | 313 => effect_target(I::Explicit, O::Item),
44        302 => effect_target(I::Explicit, O::None),
45        1..=2 | 6..=11 | 14 | 16..=17 | 19 | 24 | 31 | 35..=36 | 38 | 40..=41 | 44..=45 | 52 | 55 | 57..=59 | 62..=63 | 65..=68 | 70..=71 | 73 | 75 | 80..=82 | 90..=92 | 95..=96 | 98 | 100 | 102..=103 | 105 | 107..=108 | 111..=115 | 117 | 119..=121 | 123..=126 | 128..=130 | 132..=133 | 136..=143 | 146..=147 | 150 | 153..=154 | 157 | 159..=162 | 164..=165 | 167..=168 | 170 | 174 | 176..=177 | 184 | 187 | 189..=191 | 193 | 199 | 202..=205 | 207..=208 | 210..=212 | 214..=217 | 220..=222 | 224..=227 | 230..=232 | 236..=240 | 242 | 244 | 249..=251 | 253 | 255 | 265 | 268 | 270..=272 | 276..=277 | 279 | 281..=286 | 288..=294 | 298 | 303..=304 | 306 | 311 | 316..=317 | 335..=338 | 341 => effect_target(I::Explicit, O::Unit),
46        13 | 15 | 29 | 43 | 69 | 83 | 144..=145 | 252 => effect_target(I::Explicit, O::UnitAndDestination),
47        42 | 171 | 179 | 195..=198 | 213 | 219 | 234 | 254 | 353 => effect_target(I::None, O::Destination),
48        0 | 3..=4 | 12 | 32 | 61 | 64 | 77 | 85 | 122 | 148 | 151..=152 | 173 | 175 | 178 | 180 | 182..=183 | 185..=186 | 194 | 200..=201 | 209 | 218 | 228..=229 | 233 | 235 | 241 | 245..=248 | 256..=257 | 260 | 262 | 274..=275 | 278 | 280 | 287 | 299..=300 | 305 | 307..=310 | 312 | 314 | 318..=334 | 339..=340 | 342..=352 | 354..=355 => effect_target(I::None, O::None),
49        20..=23 | 25..=26 | 30 | 34 | 37 | 39 | 46..=49 | 51 | 60 | 74 | 78..=79 | 84 | 93..=94 | 97 | 110 | 118 | 131 | 134 | 155 => effect_target(I::None, O::Unit),
50        _ => return None,
51    };
52
53    Some(semantic)
54}
55
56#[cfg(test)]
57mod tests {
58    use googletest::prelude::*;
59    use rstest::rstest;
60
61    use super::*;
62
63    const TRINITY_TARGETING_FINGERPRINT: u64 = 0xd5cd_1066_b55a_3b00;
64
65    fn targeting_fingerprint() -> u64 {
66        (0..=355)
67            .map(|raw| spell_effect_target_semantic(raw).expect("complete Trinity effect domain"))
68            .flat_map(|semantic| [semantic.implicit as u8, semantic.object as u8])
69            .fold(0xcbf2_9ce4_8422_2325, |hash, byte| {
70                (hash ^ u64::from(byte)).wrapping_mul(0x0000_0100_0000_01b3)
71            })
72    }
73
74    #[gtest]
75    fn every_trinity_effect_id_resolves_and_out_of_domain_ids_do_not() {
76        expect_that!(
77            (0..=355).all(|raw| spell_effect_target_semantic(raw).is_some()),
78            is_true()
79        );
80        expect_that!(spell_effect_target_semantic(-1), eq(None));
81        expect_that!(spell_effect_target_semantic(356), eq(None));
82        expect_that!(spell_effect_target_semantic(i32::MAX), eq(None));
83    }
84
85    #[gtest]
86    fn complete_ordered_registry_matches_the_trinity_source_table() {
87        expect_that!(targeting_fingerprint(), eq(TRINITY_TARGETING_FINGERPRINT));
88    }
89
90    #[gtest]
91    #[rstest]
92    #[case::school_damage(2, I::Explicit, O::Unit)]
93    #[case::summon(28, I::Explicit, O::Destination)]
94    #[case::trigger_spell(64, I::None, O::None)]
95    #[case::give_currency(166, I::Caster, O::Unit)]
96    #[case::resurrect(18, I::Explicit, O::CorpseAlly)]
97    #[case::skin_player_corpse(116, I::Explicit, O::CorpseEnemy)]
98    #[case::open_lock(33, I::Explicit, O::GameObjectItem)]
99    #[case::enchant_item(53, I::Explicit, O::Item)]
100    #[case::teleport_to_return_point(13, I::Explicit, O::UnitAndDestination)]
101    #[case::jump_destination(42, I::None, O::Destination)]
102    #[case::energize(30, I::None, O::Unit)]
103    fn representative_effect_fallbacks(
104        #[case] raw: i32,
105        #[case] implicit: SpellEffectImplicitTarget,
106        #[case] object: ImplicitTargetObject,
107    ) {
108        expect_that!(
109            spell_effect_target_semantic(raw),
110            eq(Some(effect_target(implicit, object)))
111        );
112    }
113}