Skip to main content

wowlab_engine_adapter_data/
rows.rs

1use wowlab_engine_ports::{EnchantmentEffect, EnchantmentRow, ResolverError, SpellId};
2use wowlab_types::data::{EnchantmentFlat, SpellEffect};
3
4const ENCHANTMENT_EFFECT_COUNT: usize = 3;
5
6pub(crate) struct EnchantmentRowData {
7    id: i32,
8    name: String,
9    duration: i32,
10    effect_types: [i32; ENCHANTMENT_EFFECT_COUNT],
11    effect_args: [i32; ENCHANTMENT_EFFECT_COUNT],
12    effect_points_min: [Option<f64>; ENCHANTMENT_EFFECT_COUNT],
13    effect_scaling_points: [Option<f64>; ENCHANTMENT_EFFECT_COUNT],
14    scaling_class: i32,
15    item_level_min: i32,
16    item_level_max: i32,
17    max_level: i32,
18}
19
20impl From<&EnchantmentFlat> for EnchantmentRowData {
21    fn from(flat: &EnchantmentFlat) -> Self {
22        Self {
23            id: flat.id,
24            name: flat.name.clone(),
25            duration: flat.duration,
26            effect_types: [flat.effect_0, flat.effect_1, flat.effect_2],
27            effect_args: [flat.effect_arg_0, flat.effect_arg_1, flat.effect_arg_2],
28            effect_points_min: [
29                Some(f64::from(flat.effect_points_min_0)),
30                Some(f64::from(flat.effect_points_min_1)),
31                Some(f64::from(flat.effect_points_min_2)),
32            ],
33            effect_scaling_points: [
34                Some(f64::from(flat.effect_scaling_points_0)),
35                Some(f64::from(flat.effect_scaling_points_1)),
36                Some(f64::from(flat.effect_scaling_points_2)),
37            ],
38            scaling_class: flat.scaling_class,
39            item_level_min: flat.item_level_min,
40            item_level_max: flat.item_level_max,
41            max_level: flat.max_level,
42        }
43    }
44}
45
46#[derive(Debug, serde::Deserialize)]
47pub(crate) struct EnchantmentRowWire {
48    id: i32,
49    name: String,
50    #[serde(default)]
51    duration: i32,
52    effect_0: i32,
53    effect_1: i32,
54    effect_2: i32,
55    effect_arg_0: i32,
56    effect_arg_1: i32,
57    effect_arg_2: i32,
58    #[serde(default)]
59    effect_points_min_0: Option<f64>,
60    #[serde(default)]
61    effect_points_min_1: Option<f64>,
62    #[serde(default)]
63    effect_points_min_2: Option<f64>,
64    #[serde(default)]
65    effect_scaling_points_0: Option<f64>,
66    #[serde(default)]
67    effect_scaling_points_1: Option<f64>,
68    #[serde(default)]
69    effect_scaling_points_2: Option<f64>,
70    #[serde(default)]
71    scaling_class: i32,
72    #[serde(default)]
73    item_level_min: i32,
74    #[serde(default)]
75    item_level_max: i32,
76    #[serde(default)]
77    max_level: i32,
78}
79
80impl From<EnchantmentRowWire> for EnchantmentRowData {
81    fn from(wire: EnchantmentRowWire) -> Self {
82        if wire.duration < 0 {
83            tracing::warn!(
84                enchantment_id = wire.id,
85                duration_ms = wire.duration,
86                "Clamping negative enchantment duration"
87            );
88        }
89
90        Self {
91            id: wire.id,
92            name: wire.name,
93            duration: wire.duration,
94            effect_types: [wire.effect_0, wire.effect_1, wire.effect_2],
95            effect_args: [wire.effect_arg_0, wire.effect_arg_1, wire.effect_arg_2],
96            effect_points_min: [
97                wire.effect_points_min_0,
98                wire.effect_points_min_1,
99                wire.effect_points_min_2,
100            ],
101            effect_scaling_points: [
102                wire.effect_scaling_points_0,
103                wire.effect_scaling_points_1,
104                wire.effect_scaling_points_2,
105            ],
106            scaling_class: wire.scaling_class,
107            item_level_min: wire.item_level_min,
108            item_level_max: wire.item_level_max,
109            max_level: wire.max_level,
110        }
111    }
112}
113
114pub(crate) fn enchantment_row(source: impl Into<EnchantmentRowData>) -> EnchantmentRow {
115    let source = source.into();
116    let [effect_type_0, effect_type_1, effect_type_2] = source.effect_types;
117    let [effect_arg_0, effect_arg_1, effect_arg_2] = source.effect_args;
118    let [points_min_0, points_min_1, points_min_2] = source.effect_points_min;
119    let [scaling_points_0, scaling_points_1, scaling_points_2] = source.effect_scaling_points;
120    let effect = |effect_type, effect_arg, points_min, scaling_points| EnchantmentEffect {
121        effect_type,
122        effect_arg,
123        points_min,
124        scaling_points,
125    };
126    let effects = [
127        effect(effect_type_0, effect_arg_0, points_min_0, scaling_points_0),
128        effect(effect_type_1, effect_arg_1, points_min_1, scaling_points_1),
129        effect(effect_type_2, effect_arg_2, points_min_2, scaling_points_2),
130    ];
131
132    EnchantmentRow {
133        id: source.id,
134        name: source.name,
135        duration_ms: u32::try_from(source.duration).unwrap_or_default(),
136        effects,
137        scaling_class: source.scaling_class,
138        item_level_min: source.item_level_min,
139        item_level_max: source.item_level_max,
140        max_level: source.max_level,
141    }
142}
143
144pub(crate) fn spell_override_pairs(
145    rows: impl IntoIterator<Item = (i32, i32)>,
146) -> Vec<(SpellId, SpellId)> {
147    rows.into_iter()
148        .filter(|&(overrides_spell_id, _)| overrides_spell_id > 0)
149        .map(|(overrides_spell_id, spell_id)| {
150            (SpellId::new(overrides_spell_id), SpellId::new(spell_id))
151        })
152        .collect()
153}
154
155/// Keeps the racial abilities whose class mask admits `class_id` (`0` admits every class).
156pub(crate) fn racial_spell_ids(
157    rows: impl IntoIterator<Item = (i32, i32)>,
158    class_id: i32,
159) -> Vec<SpellId> {
160    let class_bit = u32::try_from(class_id - 1)
161        .ok()
162        .and_then(|shift| 1_i32.checked_shl(shift))
163        .unwrap_or_default();
164
165    rows.into_iter()
166        .filter(|&(_, class_mask)| class_mask == 0 || class_mask & class_bit != 0)
167        .map(|(spell_id, _)| SpellId::new(spell_id))
168        .collect()
169}
170
171// docref:start spell-data-validate-effect-index
172pub(crate) fn validate_effect_index(
173    spell_id: SpellId,
174    effect_index: u8,
175    effects: &[SpellEffect],
176) -> Result<SpellEffect, ResolverError> {
177    if effect_index == 0 {
178        return Err(ResolverError::spell_effect_not_found(
179            spell_id,
180            effect_index,
181        ));
182    }
183    effects
184        .get((effect_index as usize) - 1)
185        .cloned()
186        .ok_or_else(|| ResolverError::spell_effect_not_found(spell_id, effect_index))
187}
188// docref:end spell-data-validate-effect-index
189
190#[cfg(test)]
191mod tests;