1use wowlab_engine_gamedata::ResolvedGameData;
4use wowlab_engine_telemetry::{DamageEvent, DamageEventFlags, TelemetrySink};
5use wowlab_types::sim::{ActorId, EnemyIdx, SpellIdx};
6
7use super::{
8 current_spell_power,
9 dispatch::{deal_damage, deal_damage_to_target},
10 resolve_outgoing_absorbs, typed_attack_power,
11 weapon::{WeaponDamageInput, weapon_damage_input},
12};
13use crate::{
14 context::CombatCtx,
15 state::{CombatInstance, CombatProgram},
16 systems::procs::fire_impact_procs,
17};
18
19pub(crate) fn damage_flags_from_data(
20 data: &ResolvedGameData,
21 spell_id: SpellIdx,
22 physical: bool,
23) -> DamageFlags {
24 let mut flags = if physical {
25 DamageFlags::PHYSICAL
26 } else {
27 DamageFlags::empty()
28 };
29
30 if data.cannot_crit(spell_id).unwrap_or(false) {
31 flags |= DamageFlags::NO_CRIT;
32 }
33
34 if data.treat_as_periodic(spell_id).unwrap_or(false) {
35 flags |= DamageFlags::PERIODIC;
36 }
37
38 if data.treat_as_area_effect(spell_id).unwrap_or(false) {
39 flags |= DamageFlags::AOE;
40 }
41
42 if data
43 .disable_player_damage_multiplier(spell_id)
44 .unwrap_or(false)
45 {
46 flags |= DamageFlags::IGNORE_PLAYER_MULTIPLIERS;
47 }
48
49 if data
50 .disable_target_damage_multiplier(spell_id)
51 .unwrap_or(false)
52 {
53 flags |= DamageFlags::IGNORE_TARGET_MULTIPLIERS;
54 }
55
56 if data
57 .disable_positive_target_damage_multiplier(spell_id)
58 .unwrap_or(false)
59 {
60 flags |= DamageFlags::IGNORE_POSITIVE_TARGET_MULTIPLIERS;
61 }
62
63 flags
64}
65
66pub(crate) fn damage_flags_from_local(
67 program: &CombatProgram,
68 action: crate::state::program::LocalDamageActionIdx,
69 physical: bool,
70) -> DamageFlags {
71 let mut flags = program.damage.action_at(action).damage_attribute_flags;
72
73 if physical {
74 flags |= DamageFlags::PHYSICAL;
75 }
76
77 flags
78}
79
80pub(crate) fn damage_flags_from_binding(
81 program: &CombatProgram,
82 action: crate::DamageActionId,
83 physical: bool,
84) -> DamageFlags {
85 let mut flags = program
86 .damage
87 .action_id_plan(action)
88 .map_or_else(DamageFlags::empty, |plan| plan.damage_attribute_flags);
89
90 if physical {
91 flags |= DamageFlags::PHYSICAL;
92 }
93
94 flags
95}
96
97pub(in crate::systems) fn finalize_damage(
98 ctx: &mut CombatCtx<'_>,
99 target: EnemyIdx,
100 binding: crate::state::program::DamageRequestBinding,
101 event: &DamageEvent,
102) -> bool {
103 finalize_damage_with(ctx, target, binding, event, emit_damage_telemetry);
104
105 !ctx.instance.has_runtime_error()
106}
107
108pub(in crate::systems) fn emit_damage_telemetry(
109 _source: ActorId,
110 _target: EnemyIdx,
111 event: &DamageEvent,
112 _program: &CombatProgram,
113 _state: &CombatInstance,
114 sink: &mut TelemetrySink,
115) {
116 sink.emit_damage(event);
117}
118
119pub(in crate::systems) fn finalize_damage_with(
120 ctx: &mut CombatCtx<'_>,
121 target: EnemyIdx,
122 binding: crate::state::program::DamageRequestBinding,
123 event: &DamageEvent,
124 emit: impl FnOnce(
125 ActorId,
126 EnemyIdx,
127 &DamageEvent,
128 &CombatProgram,
129 &CombatInstance,
130 &mut TelemetrySink,
131 ),
132) {
133 if !ctx.instance.is_valid_target(target) {
134 return;
135 }
136
137 let amount = event.amount;
138
139 emit(
140 ctx.source,
141 target,
142 event,
143 ctx.program,
144 ctx.instance,
145 ctx.sink,
146 );
147
148 ctx.instance.runtime.total_damage += amount;
149 ctx.instance.refresh_enemy_health(target, ctx.now);
150 let before_health = ctx
151 .instance
152 .enemy_health_fraction(ctx.program, target, ctx.now)
153 .unwrap_or(1.0);
154 let death = match ctx.instance.apply_enemy_damage(target, amount, ctx.now) {
155 Ok(death) => death,
156 Err(error) => {
157 ctx.instance.record_runtime_error(error);
158
159 return;
160 }
161 };
162 let after_health = ctx
163 .instance
164 .enemy_health_fraction(ctx.program, target, ctx.now)
165 .unwrap_or(before_health);
166 let actual_damage = ctx
167 .instance
168 .enemy_max_health(ctx.program, target)
169 .map_or(amount, |maximum| {
170 ((before_health - after_health).max(0.0) * maximum).min(amount)
171 });
172
173 crate::systems::process_actor_health_change(
174 ctx.program,
175 ctx.instance,
176 ActorId::Enemy(target),
177 before_health,
178 after_health,
179 ctx.now,
180 );
181 super::leech::apply_leech_healing(ctx, binding, event, actual_damage);
182
183 if let Some(dead) = death {
184 ctx.instance.schedule(wowlab_engine_ports::Event::Death {
185 t: dead.at,
186 target: dead.enemy,
187 scope: wowlab_engine_ports::DeathEventScope::Unscoped,
188 });
189
190 if matches!(ctx.source, ActorId::Player | ActorId::Pet(_)) {
191 crate::systems::procs::fire_kill_procs(ctx, binding.effect_action(), dead.enemy);
192 }
193 }
194}
195
196pub(crate) fn resolve_spell_impact_flags(
197 ctx: &mut CombatCtx<'_>,
198 profile_action: crate::state::program::LocalDamageActionIdx,
199 flags: DamageFlags,
200) -> Option<DamageFlags> {
201 if flags.contains(DamageFlags::REFLECTION_RESOLVED) {
202 return Some(flags);
203 }
204
205 if !ctx.program.damage.has_reflection && !ctx.program.damage.has_deflection {
206 return Some(flags | DamageFlags::REFLECTION_RESOLVED);
207 }
208
209 let school = ctx.program.damage.action_at(profile_action).school;
210
211 match crate::systems::buffs::resolve_incoming_spell_defense(
212 ctx.program,
213 ctx.instance,
214 ctx.target,
215 profile_action,
216 school,
217 ctx.rng,
218 ) {
219 crate::systems::buffs::IncomingSpellDefense::None => {
220 Some(flags | DamageFlags::REFLECTION_RESOLVED)
221 }
222 crate::systems::buffs::IncomingSpellDefense::Reflected => {
223 Some(flags | DamageFlags::REFLECTION_RESOLVED | DamageFlags::REFLECTED)
224 }
225 crate::systems::buffs::IncomingSpellDefense::Deflected => None,
226 }
227}
228bitflags::bitflags! {
229 #[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
231 pub struct DamageFlags: u16 {
232 const PHYSICAL = 1 << 0;
233 const PET = 1 << 1;
235 const PERIODIC = 1 << 2;
236 const NO_CRIT = 1 << 3;
237 const GUARDIAN = 1 << 4;
239 const IGNORE_PLAYER_MULTIPLIERS = 1 << 5;
240 const IGNORE_TARGET_MULTIPLIERS = 1 << 6;
241 const IGNORE_POSITIVE_TARGET_MULTIPLIERS = 1 << 7;
242 const PROC = 1 << 8;
243 const REPORT_AS_PROFILE = 1 << 9;
245 const AOE = 1 << 10;
247 const REFLECTION_RESOLVED = 1 << 11;
249 const REFLECTED = 1 << 12;
251 const IGNORE_CRIT_DAMAGE_BONUSES = 1 << 13;
253 }
254}
255
256#[derive(Clone, Copy, Debug)]
258pub struct DamagePayload {
259 pub spell_id: u32,
260 pub profile_spell_id: Option<u32>,
261 pub coef: f64,
262 pub flags: DamageFlags,
263}
264
265impl DamagePayload {
266 #[must_use]
267 pub const fn new(spell_id: u32, coef: f64, flags: DamageFlags) -> Self {
268 Self {
269 spell_id,
270 profile_spell_id: None,
271 coef,
272 flags,
273 }
274 }
275
276 #[must_use]
277 pub const fn profiled(
278 spell_id: u32,
279 profile_spell_id: u32,
280 coef: f64,
281 flags: DamageFlags,
282 ) -> Self {
283 Self {
284 spell_id,
285 profile_spell_id: Some(profile_spell_id),
286 coef,
287 flags,
288 }
289 }
290
291 pub(super) const fn resolved_profile_spell_id(self) -> u32 {
292 match self.profile_spell_id {
293 Some(profile_spell_id) => profile_spell_id,
294 None => self.spell_id,
295 }
296 }
297}
298
299#[inline]
300pub(in crate::systems) const fn reported_damage_spell_id(request: &DamageRequest) -> u32 {
301 if request.flags.contains(DamageFlags::REPORT_AS_PROFILE) {
302 request.profile_spell_id
303 } else {
304 request.effect.spell_id
305 }
306}
307
308#[derive(Clone, Copy, Debug)]
309pub(in crate::systems) struct DamageRequest {
310 pub effect: crate::state::DamageEffectRef,
311 pub geometry_effect: crate::state::DamageEffectRef,
312 pub source_position: Option<wowlab_types::sim::SpatialTransform>,
313 pub destination: Option<wowlab_types::sim::SpatialTransform>,
314 pub profile_spell_id: u32,
315 pub base_points: Option<f64>,
316 pub coef: f64,
317 pub power: f64,
318 pub weapon: WeaponDamageInput,
319 pub flags: DamageFlags,
320}
321
322impl DamageRequest {
323 const fn new(payload: DamagePayload, power: f64) -> Self {
324 let effect = crate::state::DamageEffectRef::new(payload.spell_id, 1);
325
326 Self {
327 effect,
328 geometry_effect: effect,
329 source_position: None,
330 destination: None,
331 profile_spell_id: payload.resolved_profile_spell_id(),
332 base_points: None,
333 coef: payload.coef,
334 power,
335 weapon: WeaponDamageInput::none(),
336 flags: payload.flags,
337 }
338 }
339
340 const fn profile(mut self, spell_id: u32) -> Self {
341 self.profile_spell_id = spell_id;
342
343 self
344 }
345
346 const fn effect(mut self, effect: crate::state::DamageEffectRef) -> Self {
347 self.effect = effect;
348 self.geometry_effect = effect;
349
350 self
351 }
352
353 const fn geometry(
354 mut self,
355 effect: crate::state::DamageEffectRef,
356 source_position: Option<wowlab_types::sim::SpatialTransform>,
357 destination: Option<wowlab_types::sim::SpatialTransform>,
358 ) -> Self {
359 self.geometry_effect = effect;
360 self.source_position = source_position;
361 self.destination = destination;
362
363 self
364 }
365
366 const fn weapon(mut self, weapon: WeaponDamageInput) -> Self {
367 self.weapon = weapon;
368
369 self
370 }
371
372 const fn base_points(mut self, base_points: f64) -> Self {
373 self.base_points = Some(base_points);
374
375 self
376 }
377
378 const fn optional_base_points(mut self, base_points: Option<f64>) -> Self {
379 self.base_points = base_points;
380
381 self
382 }
383}
384
385#[derive(Clone, Copy, Debug)]
387pub(in crate::systems) struct BoundDamageRequest {
388 pub binding: crate::state::program::DamageRequestBinding,
389 input: DamageRequest,
390}
391
392impl BoundDamageRequest {
393 pub(super) const fn new(
394 input: DamageRequest,
395 binding: crate::state::program::DamageRequestBinding,
396 ) -> Self {
397 Self { binding, input }
398 }
399}
400
401impl std::ops::Deref for BoundDamageRequest {
402 type Target = DamageRequest;
403
404 fn deref(&self) -> &Self::Target {
405 &self.input
406 }
407}
408
409impl std::ops::DerefMut for BoundDamageRequest {
410 fn deref_mut(&mut self) -> &mut Self::Target {
411 &mut self.input
412 }
413}
414
415pub(crate) fn deal_residual_damage(
417 ctx: &mut CombatCtx<'_>,
418 binding: crate::state::program::DamageRequestBinding,
419 spell_id: u32,
420 amount: f64,
421) {
422 deal_bound_resolved_damage(ctx, binding, spell_id, amount, DamageFlags::PERIODIC);
423}
424
425pub(crate) fn deal_bound_resolved_damage(
427 ctx: &mut CombatCtx<'_>,
428 binding: crate::state::program::DamageRequestBinding,
429 spell_id: u32,
430 amount: f64,
431 flags: DamageFlags,
432) {
433 if amount <= 0.0 {
434 return;
435 }
436
437 if !ctx.instance.is_valid_target(ctx.target) {
438 return;
439 }
440
441 let Some(scope) = ctx.program.telemetry_scope(ctx.source, ctx.target) else {
442 return;
443 };
444
445 let resolved = resolve_outgoing_absorbs(
446 ctx.instance,
447 ctx.target,
448 ctx.program.damage.action_at(binding.effect_action()).school,
449 amount,
450 wowlab_types::combat::HitResult::Hit,
451 );
452
453 if resolved.amount > f64::EPSILON
454 && !finalize_damage(
455 ctx,
456 ctx.target,
457 binding,
458 &DamageEvent::new(
459 wowlab_engine_telemetry::DamageObservation {
460 spell_id,
461 amount: resolved.amount,
462 flags: DamageEventFlags::default()
463 .with_periodic(flags.contains(DamageFlags::PERIODIC))
464 .with_pet(flags.contains(DamageFlags::PET)),
465 },
466 ctx.now.as_millis(),
467 scope,
468 ),
469 )
470 {
471 return;
472 }
473
474 fire_impact_procs(
475 ctx,
476 crate::ImpactEvent {
477 kind: crate::state::ProcEventKind::Spell,
478 phase: crate::state::ProcPhaseMask::HIT,
479 hit_mask: resolved.hit_mask,
480 spell_id,
481 action: Some(binding.effect_action()),
482 amount: resolved.amount,
483 is_periodic: flags.contains(DamageFlags::PERIODIC),
484 is_physical: ctx.program.damage.action_at(binding.effect_action()).school
485 == wowlab_types::combat::DamageSchool::Physical,
486 swing_hand: None,
487 swing_speed_ms: 0,
488 source: ctx.source,
489 target: ctx.target,
490 provenance: crate::state::ProcProvenance {
491 trigger_spell_id: spell_id,
492 policy: crate::ProcPolicy::from_attributes(
493 ctx.program
494 .damage
495 .action_at(binding.effect_action())
496 .attributes
497 .as_ref(),
498 ),
499 kind: if flags.contains(DamageFlags::PROC) {
500 crate::state::ProcSourceKind::Proc
501 } else {
502 crate::state::ProcSourceKind::Action
503 },
504 },
505 },
506 );
507}
508
509pub(crate) fn deal_bound_damage_ap(
511 ctx: &mut CombatCtx<'_>,
512 binding: crate::state::program::DamageRequestBinding,
513 payload: DamagePayload,
514) -> (f64, bool) {
515 deal_damage_ap_typed(ctx, binding, payload, crate::state::WeaponApType::MainHand)
516}
517
518pub(crate) fn deal_damage_ap_at(
519 ctx: &mut CombatCtx<'_>,
520 target: EnemyIdx,
521 binding: crate::state::program::DamageRequestBinding,
522 payload: DamagePayload,
523) -> (f64, bool) {
524 let power = typed_attack_power(
525 ctx.program,
526 ctx.instance,
527 crate::state::WeaponApType::MainHand,
528 );
529
530 deal_damage_to_target(
531 ctx,
532 target,
533 BoundDamageRequest::new(DamageRequest::new(payload, power), binding),
534 )
535}
536
537pub(super) fn deal_damage_ap_typed(
539 ctx: &mut CombatCtx<'_>,
540 binding: crate::state::program::DamageRequestBinding,
541 payload: DamagePayload,
542 ap_type: crate::state::WeaponApType,
543) -> (f64, bool) {
544 let power = typed_attack_power(ctx.program, ctx.instance, ap_type);
545
546 deal_damage(
547 ctx,
548 BoundDamageRequest::new(DamageRequest::new(payload, power), binding),
549 )
550}
551
552pub(crate) fn deal_effect_damage_ap(
553 ctx: &mut CombatCtx<'_>,
554 binding: crate::state::program::DamageRequestBinding,
555 effect: crate::state::DamageEffectRef,
556 coef: f64,
557 flags: DamageFlags,
558) -> (f64, bool) {
559 let power = typed_attack_power(
560 ctx.program,
561 ctx.instance,
562 crate::state::WeaponApType::MainHand,
563 );
564
565 deal_damage(
566 ctx,
567 BoundDamageRequest::new(
568 DamageRequest::new(DamagePayload::new(effect.spell_id, coef, flags), power)
569 .effect(effect),
570 binding,
571 ),
572 )
573}
574
575pub(crate) fn deal_bound_effect_damage_ap_with_geometry(
576 ctx: &mut CombatCtx<'_>,
577 binding: crate::state::program::DamageRequestBinding,
578 effect: crate::state::DamageEffectRef,
579 geometry_effect: crate::state::DamageEffectRef,
580 coef: f64,
581 flags: DamageFlags,
582) -> (f64, bool) {
583 let destination = ctx.instance.actor_transform(ActorId::Enemy(ctx.target));
584 let power = typed_attack_power(
585 ctx.program,
586 ctx.instance,
587 crate::state::WeaponApType::MainHand,
588 );
589
590 deal_damage(
591 ctx,
592 BoundDamageRequest::new(
593 DamageRequest::new(DamagePayload::new(effect.spell_id, coef, flags), power)
594 .effect(effect)
595 .geometry(geometry_effect, None, destination),
596 binding,
597 ),
598 )
599}
600
601pub(crate) fn deal_effect_damage_sp_with_geometry(
602 ctx: &mut CombatCtx<'_>,
603 binding: crate::state::program::DamageRequestBinding,
604 effect: crate::state::DamageEffectRef,
605 geometry_effect: crate::state::DamageEffectRef,
606 coef: f64,
607 flags: DamageFlags,
608) -> (f64, bool) {
609 let destination = ctx.instance.actor_transform(ActorId::Enemy(ctx.target));
610
611 deal_damage(
612 ctx,
613 BoundDamageRequest::new(
614 DamageRequest::new(
615 DamagePayload::new(effect.spell_id, coef, flags),
616 current_spell_power(ctx.program, ctx.instance),
617 )
618 .effect(effect)
619 .geometry(geometry_effect, None, destination),
620 binding,
621 ),
622 )
623}
624
625pub(crate) fn deal_effect_damage_sp(
626 ctx: &mut CombatCtx<'_>,
627 binding: crate::state::program::DamageRequestBinding,
628 effect: crate::state::DamageEffectRef,
629 coef: f64,
630 flags: DamageFlags,
631) -> (f64, bool) {
632 deal_damage(
633 ctx,
634 BoundDamageRequest::new(
635 DamageRequest::new(
636 DamagePayload::new(effect.spell_id, coef, flags),
637 current_spell_power(ctx.program, ctx.instance),
638 )
639 .effect(effect),
640 binding,
641 ),
642 )
643}
644
645pub(in crate::systems) fn deal_damage_ap_with_weapon(
646 ctx: &mut CombatCtx<'_>,
647 binding: crate::state::program::DamageRequestBinding,
648 payload: DamagePayload,
649 weapon: WeaponDamageInput,
650) -> (f64, bool) {
651 let ap_type = if weapon.max > 0.0 {
652 crate::state::WeaponApType::None
653 } else {
654 crate::state::WeaponApType::MainHand
655 };
656 let power = typed_attack_power(ctx.program, ctx.instance, ap_type);
657
658 deal_damage(
659 ctx,
660 BoundDamageRequest::new(DamageRequest::new(payload, power).weapon(weapon), binding),
661 )
662}
663
664pub(crate) fn deal_bound_damage_sp(
665 ctx: &mut CombatCtx<'_>,
666 binding: crate::state::program::DamageRequestBinding,
667 payload: DamagePayload,
668) -> (f64, bool) {
669 let power = current_spell_power(ctx.program, ctx.instance);
670
671 deal_damage(
672 ctx,
673 BoundDamageRequest::new(DamageRequest::new(payload, power), binding),
674 )
675}
676
677pub(crate) fn deal_damage_sp_at(
678 ctx: &mut CombatCtx<'_>,
679 target: EnemyIdx,
680 binding: crate::state::program::DamageRequestBinding,
681 payload: DamagePayload,
682) -> (f64, bool) {
683 let power = current_spell_power(ctx.program, ctx.instance);
684
685 deal_damage_to_target(
686 ctx,
687 target,
688 BoundDamageRequest::new(DamageRequest::new(payload, power), binding),
689 )
690}
691
692pub(crate) fn deal_damage_base(
694 ctx: &mut CombatCtx<'_>,
695 binding: crate::state::program::DamageRequestBinding,
696 spell_id: u32,
697 amount: f64,
698 flags: DamageFlags,
699) -> (f64, bool) {
700 deal_damage(
701 ctx,
702 BoundDamageRequest::new(
703 DamageRequest::new(DamagePayload::new(spell_id, 0.0, flags), 0.0).base_points(amount),
704 binding,
705 ),
706 )
707}
708
709pub(crate) fn deal_effect_damage_base(
711 ctx: &mut CombatCtx<'_>,
712 binding: crate::state::program::DamageRequestBinding,
713 effect: crate::state::DamageEffectRef,
714 profile_spell_id: u32,
715 amount: f64,
716 flags: DamageFlags,
717) -> (f64, bool) {
718 deal_damage(
719 ctx,
720 BoundDamageRequest::new(
721 DamageRequest::new(DamagePayload::new(effect.spell_id, 0.0, flags), 0.0)
722 .profile(profile_spell_id)
723 .effect(effect)
724 .base_points(amount),
725 binding,
726 ),
727 )
728}
729
730pub(crate) fn deal_damage_def(
731 ctx: &mut CombatCtx<'_>,
732 binding: crate::state::program::DamageRequestBinding,
733 spell_id: u32,
734 def: crate::state::RuntimeDamageDef,
735 flags: DamageFlags,
736) {
737 deal_bound_profiled_damage_def(
738 ctx,
739 ProfiledDamageDef {
740 binding,
741 effect: crate::state::DamageEffectRef::new(spell_id, 1),
742 geometry_effect: crate::state::DamageEffectRef::new(spell_id, 1),
743 profile_spell_id: spell_id,
744 base_points: None,
745 def,
746 flags,
747 source_position: None,
748 destination: None,
749 },
750 );
751}
752
753pub(crate) fn deal_bound_profiled_damage_def(
754 ctx: &mut CombatCtx<'_>,
755 request: ProfiledDamageDef,
756) -> (f64, bool) {
757 deal_profiled_damage_def_at(ctx, &request)
758}
759
760#[derive(Clone, Copy)]
761pub(crate) struct ProfiledDamageDef {
762 pub(crate) binding: crate::state::program::DamageRequestBinding,
763 pub(crate) effect: crate::state::DamageEffectRef,
764 pub(crate) geometry_effect: crate::state::DamageEffectRef,
765 pub(crate) profile_spell_id: u32,
766 pub(crate) base_points: Option<f64>,
767 pub(crate) def: crate::state::RuntimeDamageDef,
768 pub(crate) flags: DamageFlags,
769 pub(crate) source_position: Option<wowlab_types::sim::SpatialTransform>,
770 pub(crate) destination: Option<wowlab_types::sim::SpatialTransform>,
771}
772
773pub(crate) fn deal_profiled_damage_def_at(
774 ctx: &mut CombatCtx<'_>,
775 request: &ProfiledDamageDef,
776) -> (f64, bool) {
777 let ProfiledDamageDef {
778 binding,
779 effect,
780 mut geometry_effect,
781 profile_spell_id,
782 base_points,
783 def,
784 flags,
785 source_position,
786 destination,
787 } = *request;
788
789 if geometry_effect == effect {
790 geometry_effect = ctx.program.damage.effect_ref(binding.geometry_effect);
791 }
792
793 match def {
794 crate::state::RuntimeDamageDef::None => {
795 tracing::trace!(
796 spell_id = effect.spell_id,
797 is_periodic = flags.contains(DamageFlags::PERIODIC),
798 "damage dispatch with no damage defined"
799 );
800
801 (0.0, false)
802 }
803 crate::state::RuntimeDamageDef::Flat(amount) => deal_damage(
804 ctx,
805 BoundDamageRequest::new(
806 DamageRequest::new(
807 DamagePayload::profiled(effect.spell_id, profile_spell_id, 0.0, flags),
808 0.0,
809 )
810 .effect(effect)
811 .geometry(geometry_effect, source_position, destination)
812 .base_points(amount),
813 binding,
814 ),
815 ),
816 crate::state::RuntimeDamageDef::ApCoefficient {
817 coef,
818 is_physical,
819 ap_type,
820 } => {
821 let power = typed_attack_power(ctx.program, ctx.instance, ap_type);
822
823 deal_damage(
824 ctx,
825 BoundDamageRequest::new(
826 DamageRequest::new(
827 DamagePayload::profiled(
828 effect.spell_id,
829 profile_spell_id,
830 coef,
831 if is_physical {
832 flags | DamageFlags::PHYSICAL
833 } else {
834 flags
835 },
836 ),
837 power,
838 )
839 .effect(effect)
840 .geometry(geometry_effect, source_position, destination)
841 .optional_base_points(base_points),
842 binding,
843 ),
844 )
845 }
846 crate::state::RuntimeDamageDef::SpCoefficient { coef, is_physical } => {
847 let power = current_spell_power(ctx.program, ctx.instance);
848
849 deal_damage(
850 ctx,
851 BoundDamageRequest::new(
852 DamageRequest::new(
853 DamagePayload::profiled(
854 effect.spell_id,
855 profile_spell_id,
856 coef,
857 if is_physical {
858 flags | DamageFlags::PHYSICAL
859 } else {
860 flags
861 },
862 ),
863 power,
864 )
865 .effect(effect)
866 .geometry(geometry_effect, source_position, destination)
867 .optional_base_points(base_points),
868 binding,
869 ),
870 )
871 }
872 crate::state::RuntimeDamageDef::Weapon {
873 multiplier,
874 flat_bonus,
875 normalized,
876 is_physical,
877 hand,
878 } => {
879 let power =
880 typed_attack_power(ctx.program, ctx.instance, crate::state::WeaponApType::None);
881 let weapon =
882 weapon_damage_input(ctx.program, ctx.instance, hand, multiplier, normalized);
883
884 deal_damage(
885 ctx,
886 BoundDamageRequest::new(
887 DamageRequest::new(
888 DamagePayload::profiled(
889 effect.spell_id,
890 profile_spell_id,
891 0.0,
892 if is_physical {
893 flags | DamageFlags::PHYSICAL
894 } else {
895 flags
896 },
897 ),
898 power,
899 )
900 .effect(effect)
901 .geometry(geometry_effect, source_position, destination)
902 .weapon(weapon)
903 .base_points(flat_bonus),
904 binding,
905 ),
906 )
907 }
908 }
909}