Skip to main content

wowlab_parsers/parsers/transform/
global.rs

1use wowlab_types::{
2    colors::{argb_to_hex, rgb_to_hex},
3    data::{ClassDataFlat, GlobalColorFlat, GlobalStringFlat},
4};
5
6#[cfg(feature = "dbc")]
7use super::{super::dbc::DbcData, super::errors::TransformError, resolve_icon_file_name};
8
9const DEFAULT_COLOR_COMPONENT: i32 = 255;
10
11/// Transform a single `ChrClasses` row into a flat class record.
12///
13/// # Errors
14///
15/// Returns [`TransformError`] when `class_id` is absent from the DBC data.
16pub fn transform_class(dbc: &DbcData, class_id: i32) -> Result<ClassDataFlat, TransformError> {
17    let class = dbc
18        .chr_classes
19        .get(&class_id)
20        .ok_or_else(|| TransformError::class_not_found(class_id))?;
21
22    let color = rgb_to_hex(
23        class.ClassColorR.unwrap_or(DEFAULT_COLOR_COMPONENT),
24        class.ClassColorG.unwrap_or(DEFAULT_COLOR_COMPONENT),
25        class.ClassColorB.unwrap_or(DEFAULT_COLOR_COMPONENT),
26    );
27
28    let file_name = resolve_icon_file_name(dbc, class.IconFileDataID);
29
30    Ok(ClassDataFlat {
31        id: class.ID,
32        name: class.Name_lang.clone().unwrap_or_default().into(),
33        filename: class.Filename.clone().unwrap_or_default().into(),
34        icon_file_id: class.IconFileDataID,
35        file_name: file_name.into(),
36        color: color.into(),
37        spell_class_set: class.SpellClassSet,
38        primary_stat_priority: class.PrimaryStatPriority,
39        roles_mask: class.RolesMask,
40    })
41}
42
43/// Transform every `ChrClasses` row into flat class records.
44#[must_use]
45pub fn transform_all_classes(dbc: &DbcData) -> Vec<ClassDataFlat> {
46    super::transform_all(dbc.chr_classes.keys().copied(), "class", |class_id| {
47        transform_class(dbc, class_id)
48    })
49}
50
51/// Transform a single `GlobalColor` row into a flat record.
52///
53/// # Errors
54///
55/// Returns [`TransformError`] when `color_id` is absent from the DBC data.
56pub fn transform_global_color(
57    dbc: &DbcData,
58    color_id: i32,
59) -> Result<GlobalColorFlat, TransformError> {
60    let row = dbc
61        .global_color
62        .get(&color_id)
63        .ok_or_else(|| TransformError::global_color_not_found(color_id))?;
64
65    Ok(GlobalColorFlat {
66        id: row.ID,
67        name: row.LuaConstantName.clone().into(),
68        color: argb_to_hex(row.Color).into(),
69    })
70}
71
72/// Transform every `GlobalColor` row into flat records.
73#[must_use]
74pub fn transform_all_global_colors(dbc: &DbcData) -> Vec<GlobalColorFlat> {
75    super::transform_all(
76        dbc.global_color.keys().copied(),
77        "global color",
78        |color_id| transform_global_color(dbc, color_id),
79    )
80}
81
82/// Transform a single `GlobalStrings` row into a flat localized string record.
83///
84/// # Errors
85///
86/// Returns [`TransformError`] when `string_id` is absent from the DBC data.
87pub fn transform_global_string(
88    dbc: &DbcData,
89    string_id: i32,
90) -> Result<GlobalStringFlat, TransformError> {
91    let row = dbc
92        .global_strings
93        .get(&string_id)
94        .ok_or_else(|| TransformError::global_string_not_found(string_id))?;
95
96    Ok(GlobalStringFlat {
97        id: row.ID,
98        tag: row.BaseTag.clone().into(),
99        value: row.TagText_lang.clone().unwrap_or_default(),
100        flags: row.Flags,
101    })
102}
103
104/// Transform every `GlobalStrings` row into flat records.
105#[must_use]
106pub fn transform_all_global_strings(dbc: &DbcData) -> Vec<GlobalStringFlat> {
107    super::transform_all(
108        dbc.global_strings.keys().copied(),
109        "global string",
110        |string_id| transform_global_string(dbc, string_id),
111    )
112}
113
114#[cfg(test)]
115mod tests {
116    use googletest::prelude::*;
117
118    use super::*;
119    use crate::parsers::{
120        dbc::rows::{ChrClassesRow, GlobalColorRow, GlobalStringsRow, ManifestInterfaceDataRow},
121        transform::fixtures::{class_row, dbc},
122    };
123
124    #[gtest]
125    fn transform_class_missing_errors() -> Result<()> {
126        let dbc = dbc();
127
128        verify_that!(
129            transform_class(&dbc, 999).unwrap_err().to_string(),
130            eq("Class 999 not found in DBC cache")
131        )
132    }
133
134    #[gtest]
135    fn transform_class_color_and_identity() -> Result<()> {
136        let mut dbc = dbc();
137
138        dbc.chr_classes.insert(
139            4,
140            ChrClassesRow {
141                Name_lang: Some("Rogue".to_string()),
142                SpellClassSet: 8,
143                ClassColorR: Some(255),
144                ClassColorG: Some(0),
145                ClassColorB: Some(0),
146                ..class_row(4)
147            },
148        );
149
150        let flat = transform_class(&dbc, 4)?;
151
152        verify_that!(flat.id, eq(4))?;
153        verify_that!(flat.name.as_str(), eq("Rogue"))?;
154        verify_that!(flat.spell_class_set, eq(8))?;
155
156        verify_that!(flat.color.as_str(), eq(rgb_to_hex(255, 0, 0).as_str()))
157    }
158
159    #[gtest]
160    fn transform_class_color_defaults_to_white() -> Result<()> {
161        let mut dbc = dbc();
162
163        dbc.chr_classes.insert(1, class_row(1));
164
165        let flat = transform_class(&dbc, 1)?;
166
167        verify_that!(flat.color.as_str(), eq(rgb_to_hex(255, 255, 255).as_str()))
168    }
169
170    #[gtest]
171    fn transform_class_icon_fallback_when_non_positive() -> Result<()> {
172        let mut dbc = dbc();
173
174        dbc.chr_classes.insert(
175            1,
176            ChrClassesRow {
177                IconFileDataID: 0,
178                ..class_row(1)
179            },
180        );
181
182        let flat = transform_class(&dbc, 1)?;
183
184        verify_that!(flat.file_name.as_str(), eq("inv_misc_questionmark"))
185    }
186
187    #[gtest]
188    fn transform_class_icon_resolved_from_manifest() -> Result<()> {
189        let mut dbc = dbc();
190
191        dbc.chr_classes.insert(
192            1,
193            ChrClassesRow {
194                IconFileDataID: 500,
195                ..class_row(1)
196            },
197        );
198        dbc.manifest_interface_data.insert(
199            500,
200            ManifestInterfaceDataRow {
201                ID: 500,
202                FilePath: "Interface/Icons/".to_string(),
203                FileName: "ClassIcon_Rogue.BLP".to_string(),
204            },
205        );
206
207        let flat = transform_class(&dbc, 1)?;
208
209        verify_that!(flat.file_name.as_str(), eq("classicon_rogue"))
210    }
211
212    #[gtest]
213    fn transform_global_color_missing_errors() -> Result<()> {
214        let dbc = dbc();
215
216        verify_that!(
217            transform_global_color(&dbc, 7).unwrap_err().to_string(),
218            eq("Global color 7 not found in DBC cache")
219        )
220    }
221
222    #[gtest]
223    fn transform_global_color_known_answer() -> Result<()> {
224        let mut dbc = dbc();
225        let raw = i32::from_ne_bytes(0xFFA3_35EE_u32.to_ne_bytes());
226
227        dbc.global_color.insert(
228            7,
229            GlobalColorRow {
230                ID: 7,
231                LuaConstantName: "EPIC".to_string(),
232                Color: raw,
233            },
234        );
235
236        let flat = transform_global_color(&dbc, 7)?;
237
238        verify_that!(flat.name.as_str(), eq("EPIC"))?;
239
240        verify_that!(flat.color.as_str(), eq(argb_to_hex(raw).as_str()))
241    }
242
243    #[gtest]
244    fn transform_global_string_missing_errors() -> Result<()> {
245        let dbc = dbc();
246
247        verify_that!(
248            transform_global_string(&dbc, 3).unwrap_err().to_string(),
249            eq("Global string 3 not found in DBC cache")
250        )
251    }
252
253    #[gtest]
254    fn transform_global_string_present_and_empty_value() -> Result<()> {
255        let mut dbc = dbc();
256
257        dbc.global_strings.insert(
258            3,
259            GlobalStringsRow {
260                ID: 3,
261                BaseTag: "SPELL_FAILED".to_string(),
262                TagText_lang: Some("Not ready yet.".to_string()),
263                Flags: 5,
264            },
265        );
266        dbc.global_strings.insert(
267            4,
268            GlobalStringsRow {
269                ID: 4,
270                BaseTag: "EMPTY".to_string(),
271                TagText_lang: None,
272                Flags: 0,
273            },
274        );
275
276        let present = transform_global_string(&dbc, 3)?;
277
278        verify_that!(present.tag.as_str(), eq("SPELL_FAILED"))?;
279        verify_that!(present.value.as_str(), eq("Not ready yet."))?;
280        verify_that!(present.flags, eq(5))?;
281
282        let empty = transform_global_string(&dbc, 4)?;
283
284        verify_that!(empty.value.as_str(), eq(""))
285    }
286}