1use serde::{Deserialize, Serialize};
2use wowlab_engine_macros::CopyInsert;
3
4use crate::{
5 data::gametable::{
6 CombatRatingsFlat, CombatRatingsMultByIlvlFlat, HpPerStaFlat, SpellScalingFlat,
7 },
8 sim::IntMap,
9};
10
11#[derive(Clone, Debug, Deserialize, Serialize)]
12#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
13#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
14pub struct AppliedBonus {
15 pub bonus_list_id: i32,
16 pub bonus_type: i32,
18 pub description: String,
19}
20
21#[derive(Clone, Debug, Default, Deserialize, Serialize)]
22#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
23#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
24#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
25pub struct ItemScalingData {
27 pub bonuses: IntMap<i32, Vec<ItemBonusFlat>>,
29 pub curves: IntMap<i32, CurveFlat>,
30 pub curve_points: IntMap<i32, Vec<CurvePointFlat>>,
32 pub rand_prop_points: IntMap<i32, RandPropPointsFlat>,
34 pub item_scaling_configs: IntMap<i32, ItemScalingConfigFlat>,
35 pub item_offset_curves: IntMap<i32, ItemOffsetCurveFlat>,
36 pub item_squish_eras: IntMap<i32, ItemSquishEraFlat>,
37 #[serde(default)]
39 pub combat_ratings: IntMap<i32, CombatRatingsFlat>,
40 #[serde(default)]
42 pub hp_per_sta: IntMap<i32, HpPerStaFlat>,
43 #[serde(default)]
45 pub spell_scaling: IntMap<i32, SpellScalingFlat>,
46 #[serde(default)]
48 pub combat_ratings_mult_by_ilvl: IntMap<i32, CombatRatingsMultByIlvlFlat>,
49 #[serde(default)]
51 pub gem_properties: IntMap<i32, GemPropertiesFlat>,
52}
53impl ItemScalingData {
56 #[expect(
59 clippy::too_many_arguments,
60 reason = "constructor mirrors the source game table"
61 )]
62 #[must_use]
63 pub fn from_flat(
64 item_bonuses: Vec<ItemBonusFlat>,
65 curves: Vec<CurveFlat>,
66 curve_points: Vec<CurvePointFlat>,
67 rand_prop_points: Vec<RandPropPointsFlat>,
68 item_scaling_configs: Vec<ItemScalingConfigFlat>,
69 item_offset_curves: Vec<ItemOffsetCurveFlat>,
70 item_squish_eras: Vec<ItemSquishEraFlat>,
71 combat_ratings: Vec<CombatRatingsFlat>,
72 hp_per_sta: Vec<HpPerStaFlat>,
73 spell_scaling: Vec<SpellScalingFlat>,
74 combat_ratings_mult_by_ilvl: Vec<CombatRatingsMultByIlvlFlat>,
75 gem_properties: Vec<GemPropertiesFlat>,
76 ) -> Self {
77 let mut bonuses_by_list: IntMap<i32, Vec<ItemBonusFlat>> = IntMap::default();
79
80 for bonus in item_bonuses {
81 bonuses_by_list
82 .entry(bonus.parent_item_bonus_list_id)
83 .or_default()
84 .push(bonus);
85 }
86
87 let curves_by_id: IntMap<i32, CurveFlat> = curves.into_iter().map(|c| (c.id, c)).collect();
88
89 let mut points_by_curve: IntMap<i32, Vec<CurvePointFlat>> = IntMap::default();
90
91 for point in curve_points {
92 points_by_curve
93 .entry(point.curve_id)
94 .or_default()
95 .push(point);
96 }
97
98 for points in points_by_curve.values_mut() {
99 points.sort_by_key(|p| p.order_index);
100 }
101
102 let rand_prop_by_id: IntMap<i32, RandPropPointsFlat> =
103 rand_prop_points.into_iter().map(|r| (r.id, r)).collect();
104
105 let scaling_configs_by_id: IntMap<i32, ItemScalingConfigFlat> = item_scaling_configs
106 .into_iter()
107 .map(|r| (r.id, r))
108 .collect();
109
110 let offset_curves_by_id: IntMap<i32, ItemOffsetCurveFlat> =
111 item_offset_curves.into_iter().map(|r| (r.id, r)).collect();
112
113 let squish_eras_by_id: IntMap<i32, ItemSquishEraFlat> =
114 item_squish_eras.into_iter().map(|r| (r.id, r)).collect();
115
116 let combat_ratings_by_level: IntMap<i32, CombatRatingsFlat> =
117 combat_ratings.into_iter().map(|r| (r.level, r)).collect();
118
119 let hp_per_sta_by_level: IntMap<i32, HpPerStaFlat> =
120 hp_per_sta.into_iter().map(|r| (r.level, r)).collect();
121
122 let spell_scaling_by_level: IntMap<i32, SpellScalingFlat> =
123 spell_scaling.into_iter().map(|r| (r.level, r)).collect();
124
125 let combat_ratings_mult_by_ilvl: IntMap<i32, CombatRatingsMultByIlvlFlat> =
126 combat_ratings_mult_by_ilvl
127 .into_iter()
128 .map(|r| (r.item_level, r))
129 .collect();
130
131 let gem_properties_by_id: IntMap<i32, GemPropertiesFlat> =
132 gem_properties.into_iter().map(|r| (r.id, r)).collect();
133
134 Self {
135 bonuses: bonuses_by_list,
136 curves: curves_by_id,
137 curve_points: points_by_curve,
138 rand_prop_points: rand_prop_by_id,
139 item_scaling_configs: scaling_configs_by_id,
140 item_offset_curves: offset_curves_by_id,
141 item_squish_eras: squish_eras_by_id,
142 combat_ratings: combat_ratings_by_level,
143 hp_per_sta: hp_per_sta_by_level,
144 spell_scaling: spell_scaling_by_level,
145 combat_ratings_mult_by_ilvl,
146 gem_properties: gem_properties_by_id,
147 }
148 }
149}
150
151#[derive(Clone, Copy, Debug, Deserialize, Eq, PartialEq, Serialize)]
152#[repr(i32)]
153#[non_exhaustive]
154pub enum ItemQuality {
155 Poor = 0,
156 Common = 1,
157 Uncommon = 2,
158 Rare = 3,
159 Epic = 4,
160 Legendary = 5,
161 Artifact = 6,
162 Heirloom = 7,
163}
164
165#[derive(Clone, Copy, Debug, Eq, PartialEq)]
167#[non_exhaustive]
168pub enum RandPropPointsUse {
169 StatAllocation,
171 ItemEffect,
173}
174
175#[derive(Clone, Copy, Debug, Eq, PartialEq)]
177#[non_exhaustive]
178pub enum RandPropPointsBucket {
179 Good,
180 Superior,
181 Epic,
182}
183
184impl ItemQuality {
185 #[must_use]
187 pub const fn from_dbc(raw: i32) -> Option<Self> {
188 match raw {
189 0 => Some(Self::Poor),
190 1 => Some(Self::Common),
191 2 => Some(Self::Uncommon),
192 3 => Some(Self::Rare),
193 4 => Some(Self::Epic),
194 5 => Some(Self::Legendary),
195 6 => Some(Self::Artifact),
196 7 => Some(Self::Heirloom),
197 _ => None,
198 }
199 }
200
201 #[must_use]
203 pub const fn rand_prop_points_bucket(
204 self,
205 usage: RandPropPointsUse,
206 ) -> Option<RandPropPointsBucket> {
207 match usage {
208 RandPropPointsUse::StatAllocation => match self {
209 Self::Poor | Self::Common => None,
210 Self::Uncommon => Some(RandPropPointsBucket::Good),
211 Self::Rare | Self::Heirloom => Some(RandPropPointsBucket::Superior),
212 Self::Epic | Self::Legendary | Self::Artifact => Some(RandPropPointsBucket::Epic),
213 },
214 RandPropPointsUse::ItemEffect => Some(match self {
215 Self::Poor | Self::Common | Self::Uncommon => RandPropPointsBucket::Good,
216 Self::Rare => RandPropPointsBucket::Superior,
217 Self::Epic | Self::Legendary | Self::Artifact | Self::Heirloom => {
218 RandPropPointsBucket::Epic
219 }
220 }),
221 }
222 }
223}
224
225#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
226#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
227#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
228#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
229pub struct ItemBonusFlat {
230 pub id: i32,
231 pub value_0: i32,
232 pub value_1: i32,
233 pub value_2: i32,
234 pub value_3: i32,
235 pub parent_item_bonus_list_id: i32,
236 #[serde(rename = "type")]
237 #[copy(rename = "type")]
238 pub bonus_type: i32,
239 pub order_index: i32,
240}
241
242#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
243#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
244#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
245#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
246pub struct CurveFlat {
247 pub id: i32,
248 #[serde(rename = "type")]
249 #[copy(rename = "type")]
250 pub curve_type: i32,
251 pub flags: i32,
252}
253
254#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
255#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
256#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
257#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
258pub struct CurvePointFlat {
259 pub id: i32,
260 pub curve_id: i32,
261 pub order_index: i32,
262 pub pos_0: f64,
263 pub pos_1: f64,
264 pub pos_pre_squish_0: f64,
265 pub pos_pre_squish_1: f64,
266}
267
268#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
270#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
271#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
272#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
273pub struct RandPropPointsFlat {
274 pub id: i32,
275 pub damage_replace_stat_f: f64,
276 pub damage_secondary_f: f64,
277 pub damage_replace_stat: i32,
278 pub damage_secondary: i32,
279 pub epic_f_0: f64,
280 pub epic_f_1: f64,
281 pub epic_f_2: f64,
282 pub epic_f_3: f64,
283 pub epic_f_4: f64,
284 pub superior_f_0: f64,
285 pub superior_f_1: f64,
286 pub superior_f_2: f64,
287 pub superior_f_3: f64,
288 pub superior_f_4: f64,
289 pub good_f_0: f64,
290 pub good_f_1: f64,
291 pub good_f_2: f64,
292 pub good_f_3: f64,
293 pub good_f_4: f64,
294 pub epic_0: i32,
295 pub epic_1: i32,
296 pub epic_2: i32,
297 pub epic_3: i32,
298 pub epic_4: i32,
299 pub superior_0: i32,
300 pub superior_1: i32,
301 pub superior_2: i32,
302 pub superior_3: i32,
303 pub superior_4: i32,
304 pub good_0: i32,
305 pub good_1: i32,
306 pub good_2: i32,
307 pub good_3: i32,
308 pub good_4: i32,
309}
310
311impl RandPropPointsFlat {
312 #[must_use]
314 pub const fn budget(&self, bucket: RandPropPointsBucket, slot_index: usize) -> f64 {
315 match (bucket, slot_index) {
316 (RandPropPointsBucket::Epic, 0) => self.epic_f_0,
317 (RandPropPointsBucket::Epic, 1) => self.epic_f_1,
318 (RandPropPointsBucket::Epic, 2) => self.epic_f_2,
319 (RandPropPointsBucket::Epic, 3) => self.epic_f_3,
320 (RandPropPointsBucket::Epic, _) => self.epic_f_4,
321 (RandPropPointsBucket::Superior, 0) => self.superior_f_0,
322 (RandPropPointsBucket::Superior, 1) => self.superior_f_1,
323 (RandPropPointsBucket::Superior, 2) => self.superior_f_2,
324 (RandPropPointsBucket::Superior, 3) => self.superior_f_3,
325 (RandPropPointsBucket::Superior, _) => self.superior_f_4,
326 (RandPropPointsBucket::Good, 0) => self.good_f_0,
327 (RandPropPointsBucket::Good, 1) => self.good_f_1,
328 (RandPropPointsBucket::Good, 2) => self.good_f_2,
329 (RandPropPointsBucket::Good, 3) => self.good_f_3,
330 (RandPropPointsBucket::Good, _) => self.good_f_4,
331 }
332 }
333}
334
335#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
337#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
338#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
339#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
340pub struct ItemScalingConfigFlat {
341 pub id: i32,
342 pub item_offset_curve_id: i32,
343 pub item_level: i32,
344 pub required_level: i32,
345 pub item_squish_era_id: i32,
346 pub flags: i32,
347}
348
349#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
351#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
352#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
353#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
354pub struct ItemOffsetCurveFlat {
355 pub id: i32,
356 pub curve_id: i32,
357 #[serde(rename = "curve_offset")]
359 #[copy(rename = "curve_offset")]
360 pub offset: i32,
361}
362
363#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
365#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
366#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
367#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
368pub struct ItemSquishEraFlat {
369 pub id: i32,
370 pub patch: i32,
371 pub curve_id: i32,
372 pub flags: i32,
373}
374
375#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
377pub struct EnchantmentFlat {
378 pub id: i32,
379 pub name: String,
380 pub duration: i32,
381 pub flags: i32,
382 pub icon_file_data_id: i32,
383 pub item_level_min: i32,
384 pub item_level_max: i32,
385 pub item_level: i32,
386 pub charges: i32,
387 pub scaling_class: i32,
388 pub scaling_class_restricted: i32,
389 pub condition_id: i32,
390 pub min_level: i32,
391 pub max_level: i32,
392 pub required_skill_id: i32,
393 pub required_skill_rank: i32,
394 pub transmog_use_condition_id: i32,
395 pub transmog_cost: i32,
396 pub item_visual: i32,
397 pub effect_0: i32,
398 pub effect_1: i32,
399 pub effect_2: i32,
400 pub effect_arg_0: i32,
401 pub effect_arg_1: i32,
402 pub effect_arg_2: i32,
403 pub effect_points_min_0: f32,
404 pub effect_points_min_1: f32,
405 pub effect_points_min_2: f32,
406 pub effect_scaling_points_0: f32,
407 pub effect_scaling_points_1: f32,
408 pub effect_scaling_points_2: f32,
409}
410
411#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Eq, PartialEq, Serialize)]
413pub struct PermanentEnchantFlat {
414 pub enchant_id: i32,
415 pub rank: i32,
416 pub item_class: i32,
417 pub inventory_type_mask: i32,
418 pub subclass_mask: i32,
419 pub tokenized_name: String,
420}
421
422#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
423#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
424#[cfg_attr(feature = "wasm", derive(tsify::Tsify))]
425#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
426pub struct GemPropertiesFlat {
427 pub id: i32,
428 pub enchant_id: i32,
429 #[serde(rename = "type")]
430 #[copy(rename = "type")]
431 pub gem_type: i32,
432}
433
434#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
436pub struct ItemDamageScalingFlat {
437 pub id: i32,
438 pub weapon_type: String,
439 pub item_level: i32,
440 pub quality_0: f32,
441 pub quality_1: f32,
442 pub quality_2: f32,
443 pub quality_3: f32,
444 pub quality_4: f32,
445 pub quality_5: f32,
446 pub quality_6: f32,
447}
448
449impl ItemDamageScalingFlat {
450 #[must_use]
451 pub fn quality(&self, quality: i32) -> Option<f64> {
452 let value = match quality {
453 0 => self.quality_0,
454 1 => self.quality_1,
455 2 => self.quality_2,
456 3 => self.quality_3,
457 4 => self.quality_4,
458 5 => self.quality_5,
459 6 => self.quality_6,
460 _ => return None,
461 };
462
463 Some(f64::from(value))
464 }
465}
466
467#[derive(Clone, Debug, Default, Deserialize, Serialize)]
469#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
470pub struct CooldownSetSpellEntry {
471 pub spell_id: i32,
472 pub category: i32,
473 pub player_condition_id: i32,
474 pub order_index: i32,
475 pub flags: i32,
476}
477
478#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
480pub struct CooldownSetFlat {
481 pub id: i32,
482 pub chr_specialization: i32,
483 #[copy(json)]
484 pub spells: Vec<CooldownSetSpellEntry>,
485}
486
487#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
488pub struct ExpectedStatFlat {
489 pub id: i32,
490 pub expansion_id: i32,
491 pub lvl: i32,
492 pub creature_health: f32,
493 pub player_health: f32,
494 pub creature_auto_attack_dps: f32,
495 pub creature_armor: f32,
496 pub player_mana: f32,
497 pub player_primary_stat: f32,
498 pub player_secondary_stat: f32,
499 pub armor_constant: f32,
500 pub creature_spell_damage: f32,
501 pub content_set_id: i32,
502}
503
504#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
505pub struct ExpectedStatModFlat {
506 pub id: i32,
507 pub creature_health_mod: f32,
508 pub player_health_mod: f32,
509 pub creature_auto_attack_dps_mod: f32,
510 pub creature_armor_mod: f32,
511 pub player_mana_mod: f32,
512 pub player_primary_stat_mod: f32,
513 pub player_secondary_stat_mod: f32,
514 pub armor_constant_mod: f32,
515 pub creature_spell_damage_mod: f32,
516}
517
518#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
519#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
520pub struct SpecializationSpellFlat {
521 pub id: i32,
522 pub spec_id: i32,
523 pub spell_id: i32,
524 pub overrides_spell_id: i32,
525}
526
527#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
529#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
530pub struct RacialSpellFlat {
531 pub race_id: i32,
533 pub spell_id: i32,
534 pub class_mask: i32,
536}
537
538#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
539#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
540pub struct PowerTypeFlat {
541 pub id: i32,
542 pub name_global_string_tag: String,
543 pub cost_global_string_tag: String,
544 pub power_type_enum: i32,
545 pub min_power: i32,
546 pub max_base_power: i32,
547 pub center_power: i32,
548 pub default_power: i32,
549 pub display_modifier: f32,
550 pub regen_interrupt_time_ms: i32,
551 pub regen_peace: f32,
552 pub regen_combat: f32,
553 pub flags: i32,
554}
555
556#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
557pub struct ItemArmorQualityFlat {
558 pub id: i32,
559 pub qualitymod_0: f32,
560 pub qualitymod_1: f32,
561 pub qualitymod_2: f32,
562 pub qualitymod_3: f32,
563 pub qualitymod_4: f32,
564 pub qualitymod_5: f32,
565 pub qualitymod_6: f32,
566}
567
568#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
569pub struct ItemArmorShieldFlat {
571 pub id: i32,
572 pub item_level: i32,
573 pub quality_0: f32,
574 pub quality_1: f32,
575 pub quality_2: f32,
576 pub quality_3: f32,
577 pub quality_4: f32,
578 pub quality_5: f32,
579 pub quality_6: f32,
580}
581
582#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
583pub struct ItemArmorTotalFlat {
584 pub id: i32,
585 pub item_level: i32,
586 pub cloth: f32,
587 pub leather: f32,
588 pub mail: f32,
589 pub plate: f32,
590}
591
592#[derive(Clone, CopyInsert, Debug, Default, Deserialize, Serialize)]
593pub struct ArmorLocationFlat {
594 pub id: i32,
595 pub cloth_modifier: f32,
596 pub leather_modifier: f32,
597 pub chain_modifier: f32,
598 pub plate_modifier: f32,
599 pub modifier: f32,
600}
601
602#[cfg(test)]
603#[path = "scaling/tests.rs"]
604mod tests;