1use wowlab_types::data::{
4 ArmorMitigationByLvlFlat, BaseMpFlat, BaseProfessionRatingsFlat, ChallengeModeDamageFlat,
5 ChallengeModeHealthFlat, CombatRatingsFlat, CombatRatingsMultByIlvlFlat, HpPerStaFlat,
6 ItemSocketCostPerLevelFlat, NpcDamageByClassFlat, NpcTotalHpFlat, ProfessionRatingsFlat,
7 SpellScalingFlat, StaminaMultByIlvlFlat,
8};
9
10use crate::parsers::dbc::DbcData;
11
12fn rows<'a>(dbc: &'a DbcData, name: &str) -> impl Iterator<Item = &'a Vec<String>> {
13 dbc.game_tables
14 .iter()
15 .find(|t| t.name == name)
16 .into_iter()
17 .flat_map(|t| t.rows.iter())
18}
19
20fn cell_i32(r: &[String], i: usize) -> Option<i32> {
21 r.get(i)?.parse().ok()
22}
23
24fn cell_f64(r: &[String], i: usize) -> f64 {
25 r.get(i).and_then(|s| s.parse().ok()).unwrap_or(0.0)
26}
27
28fn cell_str(r: &[String], i: usize) -> String {
29 r.get(i).cloned().unwrap_or_default()
30}
31
32#[must_use]
33pub fn transform_hp_per_sta(dbc: &DbcData) -> Vec<HpPerStaFlat> {
34 rows(dbc, "HpPerSta")
35 .filter_map(|r| {
36 Some(HpPerStaFlat {
37 level: cell_i32(r, 0)?,
38 health: cell_f64(r, 1),
39 })
40 })
41 .collect()
42}
43
44#[must_use]
45pub fn transform_combat_ratings(dbc: &DbcData) -> Vec<CombatRatingsFlat> {
46 rows(dbc, "CombatRatings")
47 .filter_map(|r| {
48 Some(CombatRatingsFlat {
49 level: cell_i32(r, 0)?,
50 unused_0: cell_f64(r, 1),
51 defense_skill: cell_f64(r, 2),
52 dodge: cell_f64(r, 3),
53 parry: cell_f64(r, 4),
54 block: cell_f64(r, 5),
55 hit_melee: cell_f64(r, 6),
56 hit_ranged: cell_f64(r, 7),
57 hit_spell: cell_f64(r, 8),
58 crit_melee: cell_f64(r, 9),
59 crit_ranged: cell_f64(r, 10),
60 crit_spell: cell_f64(r, 11),
61 corruption: cell_f64(r, 12),
62 corruption_resistance: cell_f64(r, 13),
63 speed: cell_f64(r, 14),
64 resilience_crit_taken: cell_f64(r, 15),
65 resilience_player_damage: cell_f64(r, 16),
66 lifesteal: cell_f64(r, 17),
67 haste_melee: cell_f64(r, 18),
68 haste_ranged: cell_f64(r, 19),
69 haste_spell: cell_f64(r, 20),
70 avoidance: cell_f64(r, 21),
71 sturdiness: cell_f64(r, 22),
72 unused_7: cell_f64(r, 23),
73 expertise: cell_f64(r, 24),
74 armor_penetration: cell_f64(r, 25),
75 mastery: cell_f64(r, 26),
76 pvp_power: cell_f64(r, 27),
77 unused_27: cell_f64(r, 28),
78 versatility_damage_done: cell_f64(r, 29),
79 versatility_healing_done: cell_f64(r, 30),
80 versatility_damage_taken: cell_f64(r, 31),
81 unused_12: cell_f64(r, 32),
82 })
83 })
84 .collect()
85}
86
87#[must_use]
88pub fn transform_combat_ratings_mult_by_ilvl(dbc: &DbcData) -> Vec<CombatRatingsMultByIlvlFlat> {
89 rows(dbc, "CombatRatingsMultByILvl")
90 .filter_map(|r| {
91 Some(CombatRatingsMultByIlvlFlat {
92 item_level: cell_i32(r, 0)?,
93 armor_multiplier: cell_f64(r, 1),
94 weapon_multiplier: cell_f64(r, 2),
95 trinket_multiplier: cell_f64(r, 3),
96 jewelry_multiplier: cell_f64(r, 4),
97 })
98 })
99 .collect()
100}
101
102#[must_use]
103pub fn transform_stamina_mult_by_ilvl(dbc: &DbcData) -> Vec<StaminaMultByIlvlFlat> {
104 rows(dbc, "StaminaMultByILvl")
105 .filter_map(|r| {
106 Some(StaminaMultByIlvlFlat {
107 item_level: cell_i32(r, 0)?,
108 armor_multiplier: cell_f64(r, 1),
109 weapon_multiplier: cell_f64(r, 2),
110 trinket_multiplier: cell_f64(r, 3),
111 jewelry_multiplier: cell_f64(r, 4),
112 })
113 })
114 .collect()
115}
116
117#[must_use]
118pub fn transform_base_mp(dbc: &DbcData) -> Vec<BaseMpFlat> {
119 rows(dbc, "BaseMp")
120 .filter_map(|r| {
121 Some(BaseMpFlat {
122 level: cell_i32(r, 0)?,
123 rogue: cell_f64(r, 1),
124 druid: cell_f64(r, 2),
125 hunter: cell_f64(r, 3),
126 mage: cell_f64(r, 4),
127 paladin: cell_f64(r, 5),
128 priest: cell_f64(r, 6),
129 shaman: cell_f64(r, 7),
130 warlock: cell_f64(r, 8),
131 warrior: cell_f64(r, 9),
132 death_knight: cell_f64(r, 10),
133 monk: cell_f64(r, 11),
134 demon_hunter: cell_f64(r, 12),
135 evoker: cell_f64(r, 13),
136 adventurer: cell_f64(r, 14),
137 traveler: cell_f64(r, 15),
138 })
139 })
140 .collect()
141}
142
143#[must_use]
144pub fn transform_spell_scaling(dbc: &DbcData) -> Vec<SpellScalingFlat> {
145 rows(dbc, "SpellScaling")
146 .filter_map(|r| {
147 Some(SpellScalingFlat {
148 level: cell_i32(r, 0)?,
149 rogue: cell_f64(r, 1),
150 druid: cell_f64(r, 2),
151 hunter: cell_f64(r, 3),
152 mage: cell_f64(r, 4),
153 paladin: cell_f64(r, 5),
154 priest: cell_f64(r, 6),
155 shaman: cell_f64(r, 7),
156 warlock: cell_f64(r, 8),
157 warrior: cell_f64(r, 9),
158 death_knight: cell_f64(r, 10),
159 monk: cell_f64(r, 11),
160 demon_hunter: cell_f64(r, 12),
161 evoker: cell_f64(r, 13),
162 adventurer: cell_f64(r, 14),
163 traveler: cell_f64(r, 15),
164 item: cell_f64(r, 16),
165 consumable: cell_f64(r, 17),
166 gem1: cell_f64(r, 18),
167 gem2: cell_f64(r, 19),
168 gem3: cell_f64(r, 20),
169 health: cell_f64(r, 21),
170 damage_replace_stat: cell_f64(r, 22),
171 damage_secondary: cell_f64(r, 23),
172 mana_consumable: cell_f64(r, 24),
173 })
174 })
175 .collect()
176}
177
178#[must_use]
179pub fn transform_armor_mitigation_by_lvl(dbc: &DbcData) -> Vec<ArmorMitigationByLvlFlat> {
180 rows(dbc, "ArmorMitigationByLvl")
181 .filter_map(|r| {
182 Some(ArmorMitigationByLvlFlat {
183 level: cell_i32(r, 0)?,
184 constant: cell_f64(r, 1),
185 })
186 })
187 .collect()
188}
189
190#[must_use]
191pub fn transform_npc_total_hp(dbc: &DbcData) -> Vec<NpcTotalHpFlat> {
192 rows(dbc, "NpcTotalHp")
193 .filter_map(|r| {
194 Some(NpcTotalHpFlat {
195 level: cell_i32(r, 0)?,
196 rogue: cell_f64(r, 1),
197 druid: cell_f64(r, 2),
198 hunter: cell_f64(r, 3),
199 mage: cell_f64(r, 4),
200 paladin: cell_f64(r, 5),
201 priest: cell_f64(r, 6),
202 shaman: cell_f64(r, 7),
203 warlock: cell_f64(r, 8),
204 warrior: cell_f64(r, 9),
205 death_knight: cell_f64(r, 10),
206 monk: cell_f64(r, 11),
207 demon_hunter: cell_f64(r, 12),
208 })
209 })
210 .collect()
211}
212
213#[must_use]
214pub fn transform_npc_damage_by_class(dbc: &DbcData) -> Vec<NpcDamageByClassFlat> {
216 rows(dbc, "NpcDamageByClass")
217 .filter_map(|r| {
218 Some(NpcDamageByClassFlat {
219 level: cell_i32(r, 0)?,
220 rogue: cell_f64(r, 1),
221 druid: cell_f64(r, 2),
222 hunter: cell_f64(r, 3),
223 mage: cell_f64(r, 4),
224 paladin: cell_f64(r, 5),
225 priest: cell_f64(r, 6),
226 shaman: cell_f64(r, 7),
227 warlock: cell_f64(r, 8),
228 warrior: cell_f64(r, 9),
229 death_knight: cell_f64(r, 10),
230 monk: cell_f64(r, 11),
231 demon_hunter: cell_f64(r, 12),
232 })
233 })
234 .collect()
235}
236
237#[must_use]
238pub fn transform_challenge_mode_health(dbc: &DbcData) -> Vec<ChallengeModeHealthFlat> {
239 rows(dbc, "ChallengeModeHealth")
240 .filter_map(|r| {
241 Some(ChallengeModeHealthFlat {
242 challenge_level: cell_i32(r, 0)?,
243 scalar: cell_f64(r, 1),
244 })
245 })
246 .collect()
247}
248
249#[must_use]
250pub fn transform_challenge_mode_damage(dbc: &DbcData) -> Vec<ChallengeModeDamageFlat> {
251 rows(dbc, "ChallengeModeDamage")
252 .filter_map(|r| {
253 Some(ChallengeModeDamageFlat {
254 challenge_level: cell_i32(r, 0)?,
255 scalar: cell_f64(r, 1),
256 })
257 })
258 .collect()
259}
260
261#[must_use]
262pub fn transform_item_socket_cost_per_level(dbc: &DbcData) -> Vec<ItemSocketCostPerLevelFlat> {
263 rows(dbc, "ItemSocketCostPerLevel")
264 .filter_map(|r| {
265 Some(ItemSocketCostPerLevelFlat {
266 item_level: cell_i32(r, 0)?,
267 socket_cost: cell_f64(r, 1),
268 })
269 })
270 .collect()
271}
272
273#[must_use]
274pub fn transform_profession_ratings(dbc: &DbcData) -> Vec<ProfessionRatingsFlat> {
275 rows(dbc, "ProfessionRatings")
276 .map(|r| ProfessionRatingsFlat {
277 expansion: cell_str(r, 0),
278 inspiration_craft_crit: cell_f64(r, 1),
279 resourcefulness_reagent_reduction: cell_f64(r, 2),
280 finesse_gather_extra: cell_f64(r, 3),
281 deftness_gather_time_decrease: cell_f64(r, 4),
282 perception_gather_discover: cell_f64(r, 5),
283 craft_speed_time_decrease: cell_f64(r, 6),
284 multicraft_craft_extra: cell_f64(r, 7),
285 ingenuity_concentration_refund_chance: cell_f64(r, 8),
286 unused_was_perception_gather_range: cell_f64(r, 9),
287 })
288 .collect()
289}
290
291#[must_use]
292pub fn transform_base_profession_ratings(dbc: &DbcData) -> Vec<BaseProfessionRatingsFlat> {
293 rows(dbc, "BaseProfessionRatings")
294 .map(|r| BaseProfessionRatingsFlat {
295 profession: cell_str(r, 0),
296 inspiration_craft_crit: cell_f64(r, 1),
297 resourcefulness_reagent_reduction: cell_f64(r, 2),
298 finesse_gather_extra: cell_f64(r, 3),
299 deftness_gather_time_decrease: cell_f64(r, 4),
300 perception_gather_discover: cell_f64(r, 5),
301 craft_speed_time_decrease: cell_f64(r, 6),
302 multicraft_craft_extra: cell_f64(r, 7),
303 unused_was_finesse_gather_time_increase: cell_f64(r, 8),
304 unused_was_perception_gather_range: cell_f64(r, 9),
305 })
306 .collect()
307}
308
309#[cfg(test)]
310mod tests {
311 use googletest::prelude::*;
312
313 use super::*;
314 use crate::parsers::dbc::GameTable;
315
316 fn row(cells: &[&str]) -> Vec<String> {
317 cells.iter().map(ToString::to_string).collect()
318 }
319
320 #[gtest]
321 fn cell_i32_parses_or_none() -> Result<()> {
322 verify_that!(cell_i32(&row(&["5"]), 0), some(eq(5)))?;
323 verify_that!(cell_i32(&row(&["x"]), 0), none())?;
324
325 verify_that!(cell_i32(&row(&[]), 0), none())
326 }
327
328 #[gtest]
329 fn cell_f64_parses_or_zero() -> Result<()> {
330 verify_that!(cell_f64(&row(&["2.5"]), 0), near(2.5, 1e-9))?;
331 verify_that!(cell_f64(&row(&["x"]), 0), near(0.0, 1e-9))?;
332
333 verify_that!(cell_f64(&row(&[]), 5), near(0.0, 1e-9))
334 }
335
336 #[gtest]
337 fn cell_str_reads_or_default() -> Result<()> {
338 verify_that!(cell_str(&row(&["a"]), 0), eq("a"))?;
339
340 verify_that!(cell_str(&row(&[]), 0), eq(""))
341 }
342
343 fn dbc_with_table(name: &str, table_rows: Vec<Vec<String>>) -> DbcData {
344 let mut dbc = DbcData::default();
345
346 dbc.game_tables.push(GameTable {
347 name: name.to_string(),
348 columns: vec!["Level".to_string(), "Value".to_string()],
349 rows: table_rows,
350 });
351
352 dbc
353 }
354
355 #[gtest]
356 fn transform_hp_per_sta_maps_valid_row() -> Result<()> {
357 let dbc = dbc_with_table("HpPerSta", vec![row(&["1", "2.5"])]);
358 let result = transform_hp_per_sta(&dbc);
359
360 verify_that!(result.len(), eq(1))?;
361 verify_that!(result[0].level, eq(1))?;
362
363 verify_that!(result[0].health, near(2.5, 1e-9))
364 }
365
366 #[gtest]
367 fn transform_hp_per_sta_drops_non_numeric_col0() -> Result<()> {
368 let dbc = dbc_with_table("HpPerSta", vec![row(&["x", "2.5"])]);
369
370 verify_that!(transform_hp_per_sta(&dbc).len(), eq(0))
371 }
372
373 #[gtest]
374 fn transform_hp_per_sta_missing_table_is_empty() -> Result<()> {
375 let dbc = dbc_with_table("SomethingElse", vec![row(&["1", "2.5"])]);
376
377 verify_that!(transform_hp_per_sta(&dbc).len(), eq(0))
378 }
379}