wowlab_parsers/parsers/transform/
racial.rs1use wowlab_types::{data::RacialSpellFlat, sim::IntMap};
4
5#[cfg(feature = "dbc")]
6use super::super::dbc::DbcData;
7
8const RACIAL_SKILL_LINE_FLAG: i32 = 0x8000;
10
11fn race_mask(low: i32, high: i32) -> u64 {
13 u64::from(u32::from_ne_bytes(low.to_ne_bytes()))
14 | (u64::from(u32::from_ne_bytes(high.to_ne_bytes())) << u32::BITS)
15}
16
17fn racial_skill_lines(dbc: &DbcData) -> IntMap<i32, u64> {
19 let mut lines = IntMap::default();
20
21 for row in dbc.skill_race_class_info.values().flatten() {
22 if row.Flags & RACIAL_SKILL_LINE_FLAG == 0 {
23 continue;
24 }
25
26 *lines.entry(row.SkillID).or_insert(0) |= race_mask(row.RaceMasks_0, row.RaceMasks_1);
27 }
28
29 lines
30}
31
32fn playable_race_bits(dbc: &DbcData) -> Vec<(u32, i32)> {
34 dbc.chr_races
35 .values()
36 .filter(|row| row.PlayableRaceBit >= 0)
37 .map(|row| {
38 (
39 wowlab_types::numeric::i32_to_u32_nonnegative(row.PlayableRaceBit),
40 row.ID,
41 )
42 })
43 .collect()
44}
45
46#[must_use]
50pub fn transform_all_racial_spells(dbc: &DbcData) -> Vec<RacialSpellFlat> {
51 let racial_lines = racial_skill_lines(dbc);
52 let races = playable_race_bits(dbc);
53 let mut rows = Vec::new();
54
55 for ability in dbc.skill_line_ability.values().flatten() {
56 let Some(line_race_mask) = racial_lines.get(&ability.SkillLine) else {
57 continue;
58 };
59
60 if ability.Spell == 0 {
61 continue;
62 }
63
64 let mask = match race_mask(ability.RaceMasks_0, ability.RaceMasks_1) {
65 0 => *line_race_mask,
66 own => own,
67 };
68
69 rows.extend(
70 races
71 .iter()
72 .filter(|(bit, _)| mask & 1_u64.checked_shl(*bit).unwrap_or_default() != 0)
73 .map(|(_, race_id)| (*race_id, ability.Spell, ability.ClassMask)),
74 );
75 }
76
77 rows.sort_unstable();
78 rows.dedup();
79
80 rows.into_iter()
81 .map(|(race_id, spell_id, class_mask)| RacialSpellFlat {
82 race_id,
83 spell_id,
84 class_mask,
85 })
86 .collect()
87}
88
89#[cfg(test)]
90mod tests {
91 use googletest::prelude::*;
92
93 use super::*;
94 use crate::parsers::{
95 dbc::rows::{ChrRacesRow, SkillLineAbilityRow, SkillRaceClassInfoRow},
96 transform::fixtures::dbc,
97 };
98
99 fn racial_dbc() -> DbcData {
101 let mut dbc = dbc();
102
103 dbc.chr_races.insert(
104 2,
105 ChrRacesRow {
106 ID: 2,
107 PlayableRaceBit: 1,
108 },
109 );
110 dbc.chr_races.insert(
111 26,
112 ChrRacesRow {
113 ID: 26,
114 PlayableRaceBit: 25,
115 },
116 );
117 dbc.skill_race_class_info.insert(
118 125,
119 vec![SkillRaceClassInfoRow {
120 ID: 70,
121 SkillID: 125,
122 ClassMask: 16383,
123 Flags: 32914,
124 RaceMasks_0: 2,
125 RaceMasks_1: 0,
126 }],
127 );
128 dbc.skill_race_class_info.insert(
129 899,
130 vec![SkillRaceClassInfoRow {
131 ID: 1127,
132 SkillID: 899,
133 ClassMask: 16383,
134 Flags: 32914,
135 RaceMasks_0: 33_554_432,
136 RaceMasks_1: 0,
137 }],
138 );
139 dbc.skill_race_class_info.insert(
141 185,
142 vec![SkillRaceClassInfoRow {
143 ID: 282,
144 SkillID: 185,
145 ClassMask: -1,
146 Flags: 128,
147 RaceMasks_0: -1,
148 RaceMasks_1: -1,
149 }],
150 );
151
152 dbc
153 }
154
155 #[gtest]
156 fn racial_spells_expand_the_row_race_mask() -> Result<()> {
157 let mut dbc = racial_dbc();
158
159 dbc.skill_line_ability.insert(
160 21563,
161 vec![SkillLineAbilityRow {
162 ID: 12041,
163 SkillLine: 125,
164 Spell: 21563,
165 ClassMask: 16383,
166 RaceMasks_0: 2,
167 RaceMasks_1: 0,
168 }],
169 );
170
171 verify_that!(
172 transform_all_racial_spells(&dbc),
173 elements_are![matches_pattern!(RacialSpellFlat {
174 race_id: eq(&2),
175 spell_id: eq(&21563),
176 class_mask: eq(&16383),
177 })]
178 )
179 }
180
181 #[gtest]
182 fn racial_spells_fall_back_to_the_skill_line_race_mask() -> Result<()> {
183 let mut dbc = racial_dbc();
184
185 dbc.skill_line_ability.insert(
186 107_079,
187 vec![SkillLineAbilityRow {
188 ID: 40_000,
189 SkillLine: 899,
190 Spell: 107_079,
191 ClassMask: 0,
192 RaceMasks_0: 0,
193 RaceMasks_1: 0,
194 }],
195 );
196
197 verify_that!(
198 transform_all_racial_spells(&dbc),
199 elements_are![matches_pattern!(RacialSpellFlat {
200 race_id: eq(&26),
201 spell_id: eq(&107_079),
202 class_mask: eq(&0),
203 })]
204 )
205 }
206
207 #[gtest]
208 fn racial_spells_skip_non_racial_skill_lines() -> Result<()> {
209 let mut dbc = racial_dbc();
210
211 dbc.skill_line_ability.insert(
212 33_277,
213 vec![SkillLineAbilityRow {
214 ID: 41_000,
215 SkillLine: 185,
216 Spell: 33_277,
217 ClassMask: 0,
218 RaceMasks_0: 2,
219 RaceMasks_1: 0,
220 }],
221 );
222
223 verify_that!(transform_all_racial_spells(&dbc), elements_are![])
224 }
225}