Skip to main content

wowlab_parsers/parsers/transform/
gametable.rs

1// #t(file: rust_magic_numbers) positional column indices are inherent to game table parsing
2
3use 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]
214// #t(rust_similar_fns) NPC damage and health tables have parallel schemas but remain distinct generated data contracts.
215pub 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}