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