wowlab_engine_combat/systems/buffs/
costs.rs1use super::*;
2
3pub(crate) fn current_resource_gain_mult(
5 view: &CombatView<'_>,
6 source: crate::state::ResourceGainSource,
7) -> f64 {
8 let totals = view.buff_totals();
9 let mut multiplier = totals.resource_gain_mult;
10
11 if view.state.config.base_stats.resource_type == Some(wowlab_types::combat::ResourceType::Rage)
12 && matches!(source, crate::state::ResourceGainSource::AutoAttack)
13 {
14 multiplier *= totals.rage_gain_mult;
15 multiplier *= view
16 .state
17 .config
18 .game_data
19 .power_auto_attack_gain_multiplier(i32::from(u8::from(
20 wowlab_types::combat::ResourceType::Rage,
21 )));
22 }
23
24 multiplier
25}
26
27pub(crate) fn effective_spell_cost(
28 view: &CombatView<'_>,
29 spell_id: u32,
30 base_cost: f64,
31 pool: ResourcePool,
32) -> f64 {
33 let CombatView { state, buf, .. } = view;
34 let mut percent_mult = 1.0;
35 let mut resource_cost_percent = 0.0;
36 let mut flat = 0.0;
37 let idx = wowlab_types::sim::SpellIdx::from_raw(spell_id);
38 let pool_entry = state
39 .config
40 .game_data
41 .cost_entry(idx, pool == ResourcePool::Secondary);
42 let pool_type = match pool {
43 ResourcePool::Primary => state.config.base_stats.resource_type,
44 ResourcePool::Secondary => state.config.base_stats.secondary_resource_type,
45 };
46 let divisor = pool_type.map_or(1.0, |t| {
47 state
48 .config
49 .game_data
50 .power_display_divisor(i32::from(u8::from(t)))
51 });
52
53 if let Some(resource_type) = pool_type {
54 resource_cost_percent += state
55 .config
56 .game_data
57 .power_cost_percent(i32::from(u8::from(resource_type)));
58 }
59
60 for_each_active_aura_effect(state, buf, |stacks, effect| match effect {
61 BuffEffect::ResourceCostPercent(value, resource_type)
62 if Some(*resource_type) == pool_type =>
63 {
64 resource_cost_percent += value * stacks;
65 }
66 BuffEffect::SpellCostPercent(value, spells, effect_pool)
67 if *effect_pool == pool && spells.contains(&spell_id) =>
68 {
69 percent_mult *= 1.0 + value * stacks / HUNDRED;
70 }
71 BuffEffect::SpellCostFlat(value, spells, effect_pool)
72 if *effect_pool == pool && spells.contains(&spell_id) =>
73 {
74 flat += value * stacks;
75 }
76 BuffEffect::SpellCostFromEffect {
77 value,
78 source_spell_id,
79 effect_index,
80 cost_entry,
81 operation,
82 } if pool_entry != 0
83 && *cost_entry == pool_entry
84 && state.config.game_data.effect_affects_spell(
85 wowlab_types::sim::SpellIdx::from_raw(*source_spell_id),
86 *effect_index,
87 idx,
88 ) =>
89 {
90 let points = dbc_effect_points(state, *source_spell_id, *effect_index, stacks);
91
92 match operation {
93 ModifierOperation::Flat => flat += value * points / divisor,
94 ModifierOperation::Percent => {
95 percent_mult *= 1.0 + value * points / HUNDRED;
96 }
97 _ => {}
98 }
99 }
100 _ => {}
101 });
102 percent_mult *= 1.0 + resource_cost_percent / HUNDRED;
103
104 ((base_cost + flat) * percent_mult).max(0.0)
105}
106
107pub(crate) fn base_primary_spell_cost(
108 state: &CombatState,
109 buf: &DenseBuffer,
110 spell: &crate::state::SpellData,
111) -> f64 {
112 let base = state.config.base_stats.resource_max;
113 let maximum = state
114 .config
115 .base_stats
116 .resource_type
117 .and_then(|resource_type| buf.resource(resource_type))
118 .map_or(base, |resource| resource.max);
119
120 spell.cost.resource_cost + spell.cost.resource_cost_pct.resolve(base, maximum)
121}
122
123pub(crate) fn base_optional_primary_spell_cost(
124 state: &CombatState,
125 buf: &DenseBuffer,
126 spell: &crate::state::SpellData,
127) -> f64 {
128 let base = state.config.base_stats.resource_max;
129 let maximum = state
130 .config
131 .base_stats
132 .resource_type
133 .and_then(|resource_type| buf.resource(resource_type))
134 .map_or(base, |resource| resource.max);
135
136 spell.cost.optional_resource_cost
137 + spell.cost.optional_resource_cost_pct.resolve(base, maximum)
138 + spell.cost.maximum_resource_cost_pct.resolve(base, maximum)
139}
140
141pub(crate) fn sync_player_health_from_stamina(state: &mut CombatState, buf: &DenseBuffer) {
142 let view = CombatView::new(state, buf);
143 let totals = *view.buff_totals();
144 let stamina = crate::systems::damage_pipeline::dynamic_stamina(state, &totals);
145 let Some(health_per_stamina) = state
146 .config
147 .game_data
148 .health_per_stamina(state.config.game_data.level())
149 else {
150 return;
151 };
152
153 state
154 .runtime
155 .health
156 .player_health
157 .set_maximum_clamp_current(stamina.floor() * health_per_stamina);
158}
159
160pub(crate) fn effective_optional_spell_cost(
161 view: &CombatView<'_>,
162 spell_id: u32,
163 base_optional_cost: f64,
164 pool: ResourcePool,
165) -> f64 {
166 let CombatView { state, buf, .. } = view;
167 let pool_type = match pool {
168 ResourcePool::Primary => state.config.base_stats.resource_type,
169 ResourcePool::Secondary => state.config.base_stats.secondary_resource_type,
170 };
171 let Some(pool_type) = pool_type else {
172 return base_optional_cost.max(0.0);
173 };
174 let raw_pool_type = i32::from(u8::from(pool_type));
175 let mut adjustment = 0.0;
176
177 let divisor = state.config.game_data.power_display_divisor(raw_pool_type);
178
179 for_each_active_aura_effect(state, buf, |stacks, effect| {
180 let BuffEffect::AdditionalPowerCostFromEffect {
181 value,
182 source_spell_id,
183 effect_index,
184 resource_type,
185 } = effect
186 else {
187 return;
188 };
189
190 if *resource_type == raw_pool_type
191 && state.config.game_data.effect_affects_spell(
192 wowlab_types::sim::SpellIdx::from_raw(*source_spell_id),
193 *effect_index,
194 wowlab_types::sim::SpellIdx::from_raw(spell_id),
195 )
196 {
197 adjustment += value * stacks / divisor;
198 }
199 });
200
201 (base_optional_cost + adjustment).max(0.0)
202}
203
204pub(crate) fn sync_spell_costs(state: &CombatState, buf: &mut DenseBuffer) {
205 for spell in &state.defs.spells {
206 let base_primary_cost = base_primary_spell_cost(state, buf, spell);
207 let cost = effective_spell_cost(
208 &CombatView::new(state, buf),
209 spell.spell_id,
210 base_primary_cost,
211 ResourcePool::Primary,
212 );
213 let secondary_cost = effective_spell_cost(
214 &CombatView::new(state, buf),
215 spell.spell_id,
216 spell.cost.secondary_resource_cost,
217 ResourcePool::Secondary,
218 );
219
220 if let Some(slot) = buf.spell_mut(wowlab_types::sim::SpellIdx::from_raw(spell.spell_id)) {
221 slot.cost = cost;
222 slot.secondary_cost = secondary_cost;
223 }
224 }
225}