wowlab_engine_domain/dbc/game_data/
scaling.rs1use wowlab_engine_gamedata::{RatingMultiplierSlot, ResolvedGameTables};
4use wowlab_engine_ports::EnchantmentEffect;
5use wowlab_types::data::SpellScalingFlat;
6
7use super::super::EffectLookup;
8
9pub const EXPANSION_LEVEL_SCALING: i32 = -2;
11pub const SCALING_CLASS_PRIMARY_STAT: i32 = -1;
13pub const SCALING_CLASS_DAMAGE_REPLACE_STAT: i32 = -8;
15pub const SCALING_CLASS_ARMOR_MULTIPLIED: i32 = -7;
17pub const SCALING_CLASS_DAMAGE_SECONDARY: i32 = -9;
19pub const CR_MULTIPLIER_BASE_ILVL: i32 = 1;
21
22#[must_use]
24pub fn spell_scaling_budget(
25 tables: &ResolvedGameTables,
26 scaling_class: i32,
27 level: u32,
28) -> Option<f64> {
29 let row = tables.spell_scaling_row(level)?;
30 let budget = if scaling_class == SCALING_CLASS_DAMAGE_REPLACE_STAT {
31 row.damage_replace_stat
32 } else {
33 row.item
34 };
35
36 if scaling_class == SCALING_CLASS_ARMOR_MULTIPLIED {
37 let mult = crate::stats::rating_multiplier_at_ilvl(
38 tables,
39 RatingMultiplierSlot::Armor,
40 CR_MULTIPLIER_BASE_ILVL,
41 );
42
43 Some(budget * mult)
44 } else {
45 Some(budget)
46 }
47}
48
49#[must_use]
51pub fn item_effect_scaling_budget(
52 tables: &ResolvedGameTables,
53 scaling_class: i32,
54 item_level: i32,
55) -> Option<f64> {
56 let row = tables.rand_prop_points_row(item_level)?;
57
58 match scaling_class {
59 SCALING_CLASS_DAMAGE_REPLACE_STAT => Some(row.damage_replace_stat_f),
60 SCALING_CLASS_DAMAGE_SECONDARY => Some(row.damage_secondary_f),
61 SCALING_CLASS_ARMOR_MULTIPLIED => Some(
62 row.good_f_0
63 * crate::stats::rating_multiplier_at_ilvl(
64 tables,
65 RatingMultiplierSlot::Trinket,
66 item_level,
67 ),
68 ),
69 _ => Some(row.good_f_0),
70 }
71}
72
73#[must_use]
75pub fn item_scaled_effect_value(lookup: EffectLookup<'_>, item_level: i32) -> Option<f64> {
76 let coefficient = lookup.coefficient();
77 let scaling_class = lookup.scaling_class();
78 let budget = item_effect_scaling_budget(lookup.data.game_tables(), scaling_class, item_level)?;
79
80 Some(coefficient * budget)
81}
82
83fn enchant_scaling_column(row: &SpellScalingFlat, class: i32) -> Option<f64> {
85 match class {
86 -10 => Some(row.mana_consumable),
88 -2 => Some(row.consumable),
89 -3 => Some(row.gem1),
90 -4 => Some(row.gem2),
91 -5 => Some(row.gem3),
92 -6 => Some(row.health),
93 -9 => Some(row.damage_secondary),
94 1 => Some(row.warrior),
95 10 => Some(row.monk),
96 11 => Some(row.druid),
97 12 => Some(row.demon_hunter),
98 13 => Some(row.evoker),
99 2 => Some(row.paladin),
100 3 => Some(row.hunter),
101 4 => Some(row.rogue),
102 5 => Some(row.priest),
103 6 => Some(row.death_knight),
104 7 => Some(row.shaman),
105 8 => Some(row.mage),
106 9 => Some(row.warlock),
107 SCALING_CLASS_DAMAGE_REPLACE_STAT => Some(row.damage_replace_stat),
108 SCALING_CLASS_PRIMARY_STAT | SCALING_CLASS_ARMOR_MULTIPLIED => Some(row.item),
109 _ => None,
111 }
112}
113
114#[must_use]
116pub fn enchantment_stat_value(
117 effect: &EnchantmentEffect,
118 tables: &ResolvedGameTables,
119 scaling_class: i32,
120 max_level: i32,
121 player_level: u32,
122) -> Option<f64> {
123 let scaling_points = effect.scaling_points.unwrap_or(0.0);
124
125 if scaling_class == 0 || scaling_points.abs() < f64::EPSILON {
126 return effect.points_min.filter(|p| p.abs() >= f64::EPSILON);
127 }
128
129 let level = if max_level > 0 {
130 player_level.min(u32::try_from(max_level).unwrap_or(0))
131 } else {
132 player_level
133 };
134 let row = tables.spell_scaling_row(level)?;
135 let budget = enchant_scaling_column(row, scaling_class)?;
136
137 Some((budget * scaling_points).round())
138}