1use wowlab_engine_domain::constants::OFF_HAND_DAMAGE_MULT;
4use wowlab_types::sim::{ActorId, EnemyIdx, PetIdx, SpellIdx};
5
6use super::{DamageOps, HookCtx};
7use crate::{
8 state::{RuntimeDamageDef, SwingHand, resolved_damage_base_points},
9 systems,
10};
11
12#[derive(Clone, Copy, Debug, Default, PartialEq)]
14#[must_use]
15pub struct DamageOutcome {
16 pub amount: f64,
17 pub is_crit: bool,
18}
19
20impl From<(f64, bool)> for DamageOutcome {
21 fn from((amount, is_crit): (f64, bool)) -> Self {
22 Self { amount, is_crit }
23 }
24}
25
26impl DamageOps for HookCtx<'_> {
27 fn deal_damage_ap(&mut self, spell_id: u32, coef: f64, flags: systems::DamageFlags) {
28 let _ = self.deal_damage_ap_result(spell_id, coef, flags);
29 }
30
31 fn deal_damage_ap_result(
33 &mut self,
34 spell_id: u32,
35 coef: f64,
36 flags: systems::DamageFlags,
37 ) -> DamageOutcome {
38 let Some(target) = self.target else {
39 return DamageOutcome::default();
40 };
41 let flags = self.source_flags(flags);
42 let coef = self.resolved_pet_coefficient(
43 flags,
44 wowlab_engine_domain::dbc::PetStatKind::AttackPowerInheritance,
45 coef,
46 );
47 let mut ctx = self.combat_ctx(target);
48
49 systems::deal_damage_ap(&mut ctx, systems::DamagePayload::new(spell_id, coef, flags)).into()
50 }
51
52 fn deal_damage_from_result(
59 &mut self,
60 spell_id: u32,
61 parent: DamageOutcome,
62 multiplier: f64,
63 flags: systems::DamageFlags,
64 ) -> DamageOutcome {
65 if parent.amount <= 0.0 || multiplier <= 0.0 {
66 return DamageOutcome::default();
67 }
68
69 let Some(target) = self.target else {
70 return DamageOutcome::default();
71 };
72 let flags = self.source_flags(
73 flags | systems::DamageFlags::NO_CRIT | systems::DamageFlags::IGNORE_PLAYER_MULTIPLIERS,
74 );
75 let mut ctx = self.combat_ctx(target);
76
77 systems::deal_damage_base(&mut ctx, spell_id, parent.amount * multiplier, flags).into()
78 }
79
80 fn deal_profiled_damage_ap(
82 &mut self,
83 spell_id: u32,
84 profile_spell_id: u32,
85 coef: f64,
86 flags: systems::DamageFlags,
87 ) {
88 let Some(target) = self.target else {
89 return;
90 };
91 let flags = self.source_flags(flags);
92 let coef = self.resolved_pet_coefficient(
93 flags,
94 wowlab_engine_domain::dbc::PetStatKind::AttackPowerInheritance,
95 coef,
96 );
97 let mut ctx = self.combat_ctx(target);
98
99 systems::deal_damage_ap(
100 &mut ctx,
101 systems::DamagePayload::profiled(spell_id, profile_spell_id, coef, flags),
102 );
103 }
104
105 fn haste_multiplier(&self) -> f64 {
107 systems::current_haste_mult(&self.view().combat())
108 }
109
110 fn deal_damage_ap_to_target(
112 &mut self,
113 target: EnemyIdx,
114 spell_id: u32,
115 coef: f64,
116 flags: systems::DamageFlags,
117 ) {
118 let flags = self.source_flags(flags);
119 let coef = self.resolved_pet_coefficient(
120 flags,
121 wowlab_engine_domain::dbc::PetStatKind::AttackPowerInheritance,
122 coef,
123 );
124 let mut ctx = self.combat_ctx(target);
125
126 systems::deal_damage_ap_at(
127 &mut ctx,
128 target,
129 systems::DamagePayload::new(spell_id, coef, flags),
130 );
131 }
132
133 fn deal_damage_ap_if_positive(
134 &mut self,
135 spell_id: u32,
136 coefficient: f64,
137 flags: systems::DamageFlags,
138 ) {
139 if coefficient > 0.0 {
140 self.deal_damage_ap(spell_id, coefficient, flags);
141 }
142 }
143
144 fn deal_effect_damage_ap(
145 &mut self,
146 effect: (u32, u8),
147 multiplier: f64,
148 flags: systems::DamageFlags,
149 ) {
150 let coefficient = self.effect_ap_coefficient(effect) * multiplier;
151
152 if coefficient <= 0.0 {
153 return;
154 }
155
156 let Some(target) = self.target else {
157 return;
158 };
159 let flags = self.source_flags(flags);
160 let coefficient = self.resolved_pet_coefficient(
161 flags,
162 wowlab_engine_domain::dbc::PetStatKind::AttackPowerInheritance,
163 coefficient,
164 );
165 let mut ctx = self.combat_ctx(target);
166
167 systems::deal_effect_damage_ap(
168 &mut ctx,
169 crate::state::DamageEffectRef::new(effect.0, effect.1),
170 coefficient,
171 flags,
172 );
173 }
174
175 fn deal_effect_damage_ap_with_geometry(
176 &mut self,
177 effect: (u32, u8),
178 geometry_effect: (u32, u8),
179 multiplier: f64,
180 flags: systems::DamageFlags,
181 ) {
182 let coefficient = self.effect_ap_coefficient(effect) * multiplier;
183
184 if coefficient <= 0.0 {
185 return;
186 }
187
188 let Some(target) = self.target else {
189 return;
190 };
191 let flags = self.source_flags(flags);
192 let coefficient = self.resolved_pet_coefficient(
193 flags,
194 wowlab_engine_domain::dbc::PetStatKind::AttackPowerInheritance,
195 coefficient,
196 );
197 let mut ctx = self.combat_ctx(target);
198
199 systems::deal_effect_damage_ap_with_geometry(
200 &mut ctx,
201 crate::state::DamageEffectRef::new(effect.0, effect.1),
202 crate::state::DamageEffectRef::new(geometry_effect.0, geometry_effect.1),
203 coefficient,
204 flags,
205 );
206 }
207
208 fn deal_effect_damage_sp_with_geometry(
209 &mut self,
210 effect: (u32, u8),
211 geometry_effect: (u32, u8),
212 multiplier: f64,
213 flags: systems::DamageFlags,
214 ) {
215 let coefficient = self.effect_sp_coefficient(effect) * multiplier;
216
217 if coefficient <= 0.0 {
218 return;
219 }
220
221 let Some(target) = self.target else {
222 return;
223 };
224 let flags = self.source_flags(flags);
225 let coefficient = self.resolved_pet_coefficient(
226 flags,
227 wowlab_engine_domain::dbc::PetStatKind::SpellPowerInheritance,
228 coefficient,
229 );
230 let mut ctx = self.combat_ctx(target);
231
232 systems::deal_effect_damage_sp_with_geometry(
233 &mut ctx,
234 crate::state::DamageEffectRef::new(effect.0, effect.1),
235 crate::state::DamageEffectRef::new(geometry_effect.0, geometry_effect.1),
236 coefficient,
237 flags,
238 );
239 }
240
241 fn deal_physical_damage_ap_if_positive(&mut self, spell_id: u32, coefficient: f64) {
242 self.deal_damage_ap_if_positive(spell_id, coefficient, systems::DamageFlags::PHYSICAL);
243 }
244
245 fn deal_pet_damage_ap(&mut self, spell_id: u32, coefficient: f64) {
246 self.deal_damage_ap(spell_id, coefficient, systems::DamageFlags::PET);
247 }
248
249 fn deal_pet_damage_ap_for_npc(&mut self, npc_id: u32, spell_id: u32, coefficient: f64) {
251 let Some(target) = self.target else {
252 return;
253 };
254 let flags = self.source_flags(systems::DamageFlags::PET);
255 let coefficient = coefficient
256 * self
257 .state
258 .config
259 .game_data
260 .pet_attack_power_inheritance_mult(npc_id);
261 let mut ctx = self.combat_ctx_with_source(ActorId::Pet(PetIdx::PRIMARY), target);
262
263 systems::deal_damage_ap(
264 &mut ctx,
265 systems::DamagePayload::new(spell_id, coefficient, flags),
266 );
267 }
268
269 fn deal_physical_pet_damage_ap(&mut self, spell_id: u32, coefficient: f64) {
270 self.deal_damage_ap(
271 spell_id,
272 coefficient,
273 systems::DamageFlags::PHYSICAL | systems::DamageFlags::PET,
274 );
275 }
276
277 fn deal_damage_sp(&mut self, spell_id: u32, coef: f64, flags: systems::DamageFlags) {
278 let Some(target) = self.target else {
279 return;
280 };
281 let flags = self.source_flags(flags);
282 let coef = self.resolved_pet_coefficient(
283 flags,
284 wowlab_engine_domain::dbc::PetStatKind::SpellPowerInheritance,
285 coef,
286 );
287 let mut ctx = self.combat_ctx(target);
288
289 systems::deal_damage_sp(&mut ctx, systems::DamagePayload::new(spell_id, coef, flags));
290 }
291
292 fn deal_damage_sp_to_target(
294 &mut self,
295 target: EnemyIdx,
296 spell_id: u32,
297 coef: f64,
298 flags: systems::DamageFlags,
299 ) {
300 let flags = self.source_flags(flags);
301 let coef = self.resolved_pet_coefficient(
302 flags,
303 wowlab_engine_domain::dbc::PetStatKind::SpellPowerInheritance,
304 coef,
305 );
306 let mut ctx = self.combat_ctx(target);
307
308 systems::deal_damage_sp_at(
309 &mut ctx,
310 target,
311 systems::DamagePayload::new(spell_id, coef, flags),
312 );
313 }
314
315 fn deal_profiled_damage_sp(
316 &mut self,
317 spell_id: u32,
318 profile_spell_id: u32,
319 coef: f64,
320 flags: systems::DamageFlags,
321 ) {
322 let Some(target) = self.target else {
323 return;
324 };
325 let flags = self.source_flags(flags);
326 let coef = self.resolved_pet_coefficient(
327 flags,
328 wowlab_engine_domain::dbc::PetStatKind::SpellPowerInheritance,
329 coef,
330 );
331 let mut ctx = self.combat_ctx(target);
332
333 systems::deal_damage_sp(
334 &mut ctx,
335 systems::DamagePayload::profiled(spell_id, profile_spell_id, coef, flags),
336 );
337 }
338
339 fn deal_damage_flat(&mut self, spell_id: u32, amount: f64) {
340 let Some(target) = self.target else {
341 return;
342 };
343 let flags = systems::damage_flags_from_data(
344 &self.state.config.game_data,
345 SpellIdx::from_raw(spell_id),
346 false,
347 );
348 let flags = self.source_flags(flags);
349 let mut ctx = self.combat_ctx(target);
350
351 systems::deal_damage_def(&mut ctx, spell_id, RuntimeDamageDef::Flat(amount), flags);
352 }
353
354 fn deal_resolved_damage(&mut self, spell_id: u32, amount: f64) {
356 let Some(target) = self.target else {
357 return;
358 };
359 let flags = self.source_flags(systems::DamageFlags::empty());
360 let mut ctx = self.combat_ctx(target);
361
362 systems::deal_resolved_damage(&mut ctx, spell_id, amount, flags);
363 }
364
365 fn deal_triggered_damage(
367 &mut self,
368 source_spell_id: u32,
369 effect_index: u8,
370 multiplier: f64,
371 flags: systems::DamageFlags,
372 ) -> bool {
373 let Some(target) = self.target else {
374 return false;
375 };
376 let triggered = match wowlab_engine_domain::dbc::resolve_triggered_damage(
377 wowlab_engine_domain::dbc::EffectLookup::new(
378 &self.state.config.game_data,
379 wowlab_types::sim::EffectRef::new(
380 SpellIdx::from_raw(source_spell_id),
381 effect_index,
382 ),
383 ),
384 ) {
385 Ok(triggered) => triggered,
386 Err(error) => {
387 tracing::warn!(source_spell_id, effect_index, %error, "TRIGGER_DAMAGE_UNRESOLVED");
388
389 return false;
390 }
391 };
392 let Some(damage) =
393 crate::builder::lower_resolved_damage(triggered.damage, OFF_HAND_DAMAGE_MULT)
394 .map(|damage| damage.scaled(multiplier))
395 else {
396 return false;
397 };
398 let base_points = Some(resolved_damage_base_points(triggered.damage, multiplier));
399 let flags = self.source_flags(flags);
400 let mut ctx = self.combat_ctx(target);
401
402 systems::deal_profiled_damage_def(
403 &mut ctx,
404 crate::state::DamageEffectRef::new(
405 triggered.spell_id.as_u32(),
406 triggered.damage.effect_index,
407 ),
408 source_spell_id,
409 base_points,
410 damage,
411 flags,
412 );
413
414 true
415 }
416
417 fn deal_item_damage_sp(&mut self, spell_id: u32) {
419 const ITEM_DAMAGE_EFFECT: u8 = 1;
420 let coef = self
421 .state
422 .config
423 .game_data
424 .sp_coef(SpellIdx::from_raw(spell_id), ITEM_DAMAGE_EFFECT);
425
426 self.deal_damage_sp(spell_id, coef, systems::DamageFlags::empty());
427 }
428
429 fn player_swing_period_ms(&self, hand: SwingHand) -> Option<u32> {
430 systems::active_form_combat_round_time_ms(self.state, self.buf)
431 .or_else(|| match hand {
432 SwingHand::MainHand => Some(self.game_data().main_hand().speed_ms),
433 SwingHand::OffHand => self.game_data().off_hand().map(|weapon| weapon.speed_ms),
434 })
435 .filter(|period_ms| *period_ms > 0)
436 }
437
438 fn effect_base_points(&self, effect: (u32, u8)) -> f64 {
439 self.game_data()
440 .base_points(SpellIdx::from_raw(effect.0), effect.1)
441 }
442
443 fn effect_points(&self, effect: (u32, u8)) -> f64 {
444 self.game_data().effect_points(
445 SpellIdx::from_raw(effect.0),
446 effect.1,
447 systems::current_mastery_points(&self.view().combat()),
448 )
449 }
450
451 fn effect_percent(&self, effect: (u32, u8)) -> f64 {
452 self.effect_base_points(effect) / wowlab_types::constants::HUNDRED
453 }
454
455 fn effect_ap_coefficient(&self, effect: (u32, u8)) -> f64 {
456 self.game_data()
457 .ap_coef(SpellIdx::from_raw(effect.0), effect.1)
458 }
459
460 fn effect_sp_coefficient(&self, effect: (u32, u8)) -> f64 {
461 self.game_data()
462 .sp_coef(SpellIdx::from_raw(effect.0), effect.1)
463 }
464
465 fn target_health_fraction(&self) -> f64 {
466 self.target
467 .filter(|target| self.state.is_valid_target(*target))
468 .and_then(|target| self.state.enemy_health_fraction(target, self.now))
469 .unwrap_or(1.0)
470 }
471}