1use common::{
2 Damage, DamageSource,
3 combat::{self, DamageContributor},
4 comp::{
5 Alignment, Energy, Group, Health, HealthChange, Inventory, LightEmitter, Mass,
6 ModifierKind, PhysicsState, Player, Pos, Stats,
7 agent::{Sound, SoundKind},
8 aura::{Auras, EnteredAuras},
9 body::{Body, object},
10 buff::{
11 Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffKey, BuffKind, BuffSource,
12 Buffs, DestInfo,
13 },
14 fluid_dynamics::{Fluid, LiquidKind},
15 item::MaterialStatManifest,
16 },
17 event::{
18 BuffEvent, ChangeBodyEvent, CreateSpriteEvent, EmitExt, EnergyChangeEvent,
19 HealthChangeEvent, RemoveLightEmitterEvent, SoundEvent,
20 },
21 event_emitters,
22 outcome::Outcome,
23 resources::{DeltaTime, Secs, Time},
24 terrain::SpriteKind,
25 uid::{IdMaps, Uid},
26};
27use common_base::prof_span;
28use common_ecs::{Job, Origin, ParMode, Phase, System};
29use rayon::iter::ParallelIterator;
30use specs::{
31 Entities, Entity, LendJoin, ParJoin, Read, ReadExpect, ReadStorage, SystemData, WriteStorage,
32 shred,
33};
34use vek::Vec3;
35
36event_emitters! {
37 struct Events[EventEmitters] {
38 buff: BuffEvent,
39 change_body: ChangeBodyEvent,
40 remove_light: RemoveLightEmitterEvent,
41 health_change: HealthChangeEvent,
42 energy_change: EnergyChangeEvent,
43 sound: SoundEvent,
44 create_sprite: CreateSpriteEvent,
45 outcome: Outcome,
46 }
47}
48
49#[derive(SystemData)]
50pub struct ReadData<'a> {
51 entities: Entities<'a>,
52 dt: Read<'a, DeltaTime>,
53 events: Events<'a>,
54 inventories: ReadStorage<'a, Inventory>,
55 healths: ReadStorage<'a, Health>,
56 energies: ReadStorage<'a, Energy>,
57 physics_states: ReadStorage<'a, PhysicsState>,
58 groups: ReadStorage<'a, Group>,
59 id_maps: Read<'a, IdMaps>,
60 time: Read<'a, Time>,
61 msm: ReadExpect<'a, MaterialStatManifest>,
62 buffs: ReadStorage<'a, Buffs>,
63 auras: ReadStorage<'a, Auras>,
64 entered_auras: ReadStorage<'a, EnteredAuras>,
65 positions: ReadStorage<'a, Pos>,
66 bodies: ReadStorage<'a, Body>,
67 light_emitters: ReadStorage<'a, LightEmitter>,
68 alignments: ReadStorage<'a, Alignment>,
69 players: ReadStorage<'a, Player>,
70 uids: ReadStorage<'a, Uid>,
71 masses: ReadStorage<'a, Mass>,
72}
73
74#[derive(Default)]
75pub struct Sys;
76impl<'a> System<'a> for Sys {
77 type SystemData = (ReadData<'a>, WriteStorage<'a, Stats>);
78
79 const NAME: &'static str = "buff";
80 const ORIGIN: Origin = Origin::Common;
81 const PHASE: Phase = Phase::Create;
82
83 fn run(job: &mut Job<Self>, (read_data, mut stats): Self::SystemData) {
84 let mut emitters = read_data.events.get_emitters();
85 let dt = read_data.dt.0;
86 stats.set_event_emission(false);
88
89 job.cpu_stats.measure(ParMode::Rayon);
93 let to_put_out_campfires = (
94 &read_data.entities,
95 &read_data.bodies,
96 &read_data.physics_states,
97 &read_data.light_emitters, )
99 .par_join()
100 .map_init(
101 || {
102 prof_span!(guard, "buff campfire deactivate");
103 guard
104 },
105 |_guard, (entity, body, physics_state, _)| {
106 if matches!(*body, Body::Object(object::Body::CampfireLit))
107 && matches!(
108 physics_state.in_fluid,
109 Some(Fluid::Liquid {
110 kind: LiquidKind::Water,
111 ..
112 })
113 )
114 {
115 Some(entity)
116 } else {
117 None
118 }
119 },
120 )
121 .fold(Vec::new, |mut to_put_out_campfires, put_out_campfire| {
122 put_out_campfire.map(|put| to_put_out_campfires.push(put));
123 to_put_out_campfires
124 })
125 .reduce(
126 Vec::new,
127 |mut to_put_out_campfires_a, mut to_put_out_campfires_b| {
128 to_put_out_campfires_a.append(&mut to_put_out_campfires_b);
129 to_put_out_campfires_a
130 },
131 );
132 job.cpu_stats.measure(ParMode::Single);
133 {
134 prof_span!(_guard, "write deferred campfire deletion");
135 for e in to_put_out_campfires {
138 {
139 emitters.emit(ChangeBodyEvent {
140 entity: e,
141 new_body: Body::Object(object::Body::Campfire),
142 });
143 emitters.emit(RemoveLightEmitterEvent { entity: e });
144 }
145 }
146 }
147
148 let buff_join = (
149 &read_data.entities,
150 &read_data.buffs,
151 &mut stats,
152 &read_data.bodies,
153 &read_data.healths,
154 &read_data.energies,
155 read_data.uids.maybe(),
156 read_data.physics_states.maybe(),
157 read_data.masses.maybe(),
158 )
159 .lend_join();
160 buff_join.for_each(|comps| {
161 let (entity, buff_comp, mut stat, body, health, energy, uid, physics_state, mass) =
162 comps;
163 let dest_info = DestInfo {
164 stats: Some(&stat),
165 mass,
166 };
167 if let Some(physics_state) = physics_state {
169 if let Some((_, burning)) = buff_comp.iter_kind(BuffKind::Burning).next() {
171 for t_entity in physics_state.touch_entities.keys().filter_map(|te_uid| {
172 read_data.id_maps.uid_entity(*te_uid).filter(|te| {
173 combat::permit_pvp(
174 &read_data.alignments,
175 &read_data.players,
176 &read_data.entered_auras,
177 &read_data.id_maps,
178 Some(entity),
179 *te,
180 )
181 })
182 }) {
183 let duration = burning.data.duration.map(|d| d * 0.9);
184 if duration.is_none_or(|d| d.0 >= 1.0) {
185 let source =
186 uid.map_or(BuffSource::World, |u| BuffSource::Character { by: *u });
187 emitters.emit(BuffEvent {
188 entity: t_entity,
189 buff_change: BuffChange::Add(Buff::new(
190 BuffKind::Burning,
191 BuffData::new(burning.data.strength, duration),
192 vec![BuffCategory::Natural],
193 source,
194 *read_data.time,
195 DestInfo {
196 stats: None,
199 mass: read_data.masses.get(t_entity),
200 },
201 mass,
202 )),
203 });
204 }
205 }
206 }
207 if matches!(
208 physics_state.on_ground.and_then(|b| b.get_sprite()),
209 Some(SpriteKind::EnsnaringVines)
210 ) {
211 emitters.emit(BuffEvent {
213 entity,
214 buff_change: BuffChange::Add(Buff::new(
215 BuffKind::Ensnared,
216 BuffData::new(0.5, Some(Secs(0.1))),
217 Vec::new(),
218 BuffSource::World,
219 *read_data.time,
220 dest_info,
221 None,
222 )),
223 });
224 }
225 if matches!(
226 physics_state.on_ground.and_then(|b| b.get_sprite()),
227 Some(SpriteKind::EnsnaringWeb)
228 ) {
229 emitters.emit(BuffEvent {
231 entity,
232 buff_change: BuffChange::Add(Buff::new(
233 BuffKind::Ensnared,
234 BuffData::new(1.0, Some(Secs(1.0))),
235 Vec::new(),
236 BuffSource::World,
237 *read_data.time,
238 dest_info,
239 None,
240 )),
241 });
242 }
243 if matches!(
244 physics_state.on_ground.and_then(|b| b.get_sprite()),
245 Some(SpriteKind::SeaUrchin)
246 ) {
247 emitters.emit(BuffEvent {
249 entity,
250 buff_change: BuffChange::Add(Buff::new(
251 BuffKind::Bleeding,
252 BuffData::new(1.0, Some(Secs(6.0))),
253 Vec::new(),
254 BuffSource::World,
255 *read_data.time,
256 dest_info,
257 None,
258 )),
259 });
260 }
261 if matches!(
262 physics_state.on_ground.and_then(|b| b.get_sprite()),
263 Some(SpriteKind::HaniwaTrap)
264 ) && !body.immune_to(BuffKind::Bleeding)
265 {
266 if let Some(pos) = read_data.positions.get(entity) {
268 emitters.emit(CreateSpriteEvent {
270 pos: Vec3::new(pos.0.x as i32, pos.0.y as i32, pos.0.z as i32 - 1),
271 sprite: SpriteKind::HaniwaTrapTriggered,
272 del_timeout: Some((4.0, 1.0)),
273 });
274 emitters.emit(SoundEvent {
275 sound: Sound::new(SoundKind::Trap, pos.0, 12.0, read_data.time.0),
276 });
277 emitters.emit(Outcome::Slash { pos: pos.0 });
278
279 emitters.emit(BuffEvent {
280 entity,
281 buff_change: BuffChange::Add(Buff::new(
282 BuffKind::Bleeding,
283 BuffData::new(5.0, Some(Secs(3.0))),
284 Vec::new(),
285 BuffSource::World,
286 *read_data.time,
287 dest_info,
288 None,
289 )),
290 });
291 }
292 }
293 if matches!(
294 physics_state.on_ground.and_then(|b| b.get_sprite()),
295 Some(SpriteKind::IronSpike | SpriteKind::HaniwaTrapTriggered)
296 ) {
297 emitters.emit(BuffEvent {
299 entity,
300 buff_change: BuffChange::Add(Buff::new(
301 BuffKind::Bleeding,
302 BuffData::new(1.0, Some(Secs(4.0))),
303 Vec::new(),
304 BuffSource::World,
305 *read_data.time,
306 dest_info,
307 None,
308 )),
309 });
310 }
311 if matches!(
312 physics_state.on_ground.and_then(|b| b.get_sprite()),
313 Some(SpriteKind::HotSurface)
314 ) {
315 emitters.emit(BuffEvent {
317 entity,
318 buff_change: BuffChange::Add(Buff::new(
319 BuffKind::Burning,
320 BuffData::new(10.0, None),
321 Vec::new(),
322 BuffSource::World,
323 *read_data.time,
324 dest_info,
325 None,
326 )),
327 });
328 }
329 if matches!(
330 physics_state.on_ground.and_then(|b| b.get_sprite()),
331 Some(SpriteKind::IceSpike)
332 ) {
333 emitters.emit(BuffEvent {
335 entity,
336 buff_change: BuffChange::Add(Buff::new(
337 BuffKind::Bleeding,
338 BuffData::new(15.0, Some(Secs(0.1))),
339 Vec::new(),
340 BuffSource::World,
341 *read_data.time,
342 dest_info,
343 None,
344 )),
345 });
346 emitters.emit(BuffEvent {
348 entity,
349 buff_change: BuffChange::Add(Buff::new(
350 BuffKind::Frozen,
351 BuffData::new(0.2, Some(Secs(3.0))),
352 Vec::new(),
353 BuffSource::World,
354 *read_data.time,
355 dest_info,
356 None,
357 )),
358 });
359 }
360 if matches!(
361 physics_state.on_ground.and_then(|b| b.get_sprite()),
362 Some(SpriteKind::FireBlock)
363 ) {
364 emitters.emit(BuffEvent {
366 entity,
367 buff_change: BuffChange::Add(Buff::new(
368 BuffKind::Burning,
369 BuffData::new(20.0, None),
370 Vec::new(),
371 BuffSource::World,
372 *read_data.time,
373 dest_info,
374 None,
375 )),
376 });
377 }
378 if matches!(
380 physics_state.in_fluid,
381 Some(Fluid::Liquid {
382 kind: LiquidKind::Lava,
383 ..
384 })
385 ) {
386 emitters.emit(BuffEvent {
388 entity,
389 buff_change: BuffChange::Add(Buff::new(
390 BuffKind::Burning,
391 BuffData::new(20.0, None),
392 vec![BuffCategory::Natural],
393 BuffSource::World,
394 *read_data.time,
395 dest_info,
396 None,
397 )),
398 });
399 } else if matches!(
400 physics_state.in_fluid,
401 Some(Fluid::Liquid {
402 kind: LiquidKind::Water,
403 ..
404 })
405 ) && buff_comp.kinds[BuffKind::Burning].is_some()
406 {
407 emitters.emit(BuffEvent {
409 entity,
410 buff_change: BuffChange::RemoveByKind(BuffKind::Burning),
411 });
412 }
413 }
414
415 let mut expired_buffs = Vec::<BuffKey>::new();
416
417 buff_comp
420 .buffs
421 .iter()
422 .filter_map(|(buff_key, buff)| {
423 if let Some((uid, aura_key)) = buff.cat_ids.iter().find_map(|cat_id| {
424 if let BuffCategory::FromActiveAura(uid, aura_key) = cat_id {
425 Some((uid, aura_key))
426 } else {
427 None
428 }
429 }) {
430 Some((buff_key, buff, uid, aura_key))
431 } else {
432 None
433 }
434 })
435 .for_each(|(buff_key, buff, uid, aura_key)| {
436 let replace = if let Some(aura_entity) = read_data.id_maps.uid_entity(*uid) {
437 if let Some(aura) = read_data
438 .auras
439 .get(aura_entity)
440 .and_then(|auras| auras.auras.get(*aura_key))
441 {
442 if let (Some(pos), Some(aura_pos)) = (
443 read_data.positions.get(entity),
444 read_data.positions.get(aura_entity),
445 ) {
446 pos.0.distance_squared(aura_pos.0) > aura.radius.powi(2)
447 } else {
448 true
449 }
450 } else {
451 true
452 }
453 } else {
454 true
455 };
456 if replace {
457 expired_buffs.push(buff_key);
458 emitters.emit(BuffEvent {
459 entity,
460 buff_change: BuffChange::Add(Buff::new(
461 buff.kind,
462 buff.data,
463 buff.cat_ids
464 .iter()
465 .copied()
466 .filter(|cat_id| {
467 !matches!(cat_id, BuffCategory::FromActiveAura(..))
468 })
469 .collect::<Vec<_>>(),
470 buff.source,
471 *read_data.time,
472 dest_info,
473 None,
474 )),
475 });
476 }
477 });
478
479 buff_comp.buffs.iter().for_each(|(buff_key, buff)| {
480 if buff.end_time.is_some_and(|end| end.0 < read_data.time.0) {
481 expired_buffs.push(buff_key)
482 }
483 });
484
485 let infinite_damage_reduction = (Damage::compute_damage_reduction(
486 None,
487 read_data.inventories.get(entity),
488 Some(&stat),
489 &read_data.msm,
490 ) - 1.0)
491 .abs()
492 < f32::EPSILON;
493 if infinite_damage_reduction {
494 for (key, buff) in buff_comp.buffs.iter() {
495 if !buff.kind.is_buff() {
496 expired_buffs.push(key);
497 }
498 }
499 }
500
501 stat.reset_temp_modifiers();
503
504 let mut body_override = None;
505
506 let mut buff_kinds = buff_comp
508 .kinds
509 .iter()
510 .filter_map(|(kind, keys)| keys.as_ref().map(|keys| (kind, keys.clone())))
511 .collect::<Vec<(BuffKind, (Vec<BuffKey>, Time))>>();
512 buff_kinds.sort_by_key(|(kind, _)| !kind.affects_subsequent_buffs());
513 for (buff_kind, (buff_keys, kind_start_time)) in buff_kinds.into_iter() {
514 let mut active_buff_keys = Vec::new();
515 if infinite_damage_reduction && !buff_kind.is_buff() {
516 continue;
517 }
518
519 if buff_kind.stacks() {
520 active_buff_keys = buff_keys;
522 } else {
523 active_buff_keys.push(buff_keys[0]);
525 }
526 for buff_key in active_buff_keys.into_iter() {
527 if let Some(buff) = buff_comp.buffs.get(buff_key) {
528 if buff.start_time.0 > read_data.time.0 {
530 continue;
531 }
532 let buff_owner = if let BuffSource::Character { by: owner } = buff.source {
534 Some(owner)
535 } else {
536 None
537 };
538
539 for effect in &buff.effects {
541 execute_effect(
542 effect,
543 buff.kind,
544 buff.start_time,
545 kind_start_time,
546 &read_data,
547 &mut stat,
548 body,
549 &mut body_override,
550 health,
551 energy,
552 entity,
553 buff_owner,
554 &mut emitters,
555 dt,
556 *read_data.time,
557 expired_buffs.contains(&buff_key),
558 buff_comp,
559 );
560 }
561 }
562 }
563 }
564
565 let new_body = body_override.unwrap_or(stat.original_body);
567 if new_body != *body {
568 emitters.emit(ChangeBodyEvent { entity, new_body });
569 }
570
571 if !expired_buffs.is_empty() {
573 emitters.emit(BuffEvent {
574 entity,
575 buff_change: BuffChange::RemoveByKey(expired_buffs),
576 });
577 }
578
579 if health.is_dead {
581 emitters.emit(BuffEvent {
582 entity,
583 buff_change: BuffChange::RemoveByCategory {
584 all_required: vec![],
585 any_required: vec![],
586 none_required: vec![BuffCategory::PersistOnDeath],
587 },
588 });
589 }
590 });
591 stats.set_event_emission(true);
593 }
594}
595
596#[expect(clippy::too_many_arguments)]
598fn execute_effect(
599 effect: &BuffEffect,
600 buff_kind: BuffKind,
601 buff_start_time: Time,
602 buff_kind_start_time: Time,
603 read_data: &ReadData,
604 stat: &mut Stats,
605 current_body: &Body,
606 body_override: &mut Option<Body>,
607 health: &Health,
608 energy: &Energy,
609 entity: Entity,
610 buff_owner: Option<Uid>,
611 server_emitter: &mut (
612 impl EmitExt<HealthChangeEvent> + EmitExt<EnergyChangeEvent> + EmitExt<BuffEvent>
613 ),
614 dt: f32,
615 time: Time,
616 buff_will_expire: bool,
617 buffs_comp: &Buffs,
618) {
619 let num_ticks = |tick_dur: Secs| {
620 let time_passed = time.0 - buff_start_time.0;
621 let dt = dt as f64;
622 let curr_tick = (time_passed / tick_dur.0).floor();
637 let prev_tick = ((time_passed - dt).max(0.0) / tick_dur.0).floor();
638 let whole_ticks = curr_tick - prev_tick;
639
640 if buff_will_expire {
641 let fractional_tick = (time_passed % tick_dur.0) / tick_dur.0;
644 Some((whole_ticks + fractional_tick) as f32)
645 } else if whole_ticks >= 1.0 {
646 Some(whole_ticks as f32)
647 } else {
648 None
649 }
650 };
651 match effect {
652 BuffEffect::HealthChangeOverTime {
653 rate,
654 kind,
655 instance,
656 tick_dur,
657 } => {
658 if let Some(num_ticks) = num_ticks(*tick_dur) {
659 let amount = *rate * num_ticks * tick_dur.0 as f32;
660
661 let (cause, by) = if amount != 0.0 {
662 (Some(DamageSource::Buff(buff_kind)), buff_owner)
663 } else {
664 (None, None)
665 };
666 let amount = match *kind {
667 ModifierKind::Additive => amount,
668 ModifierKind::Multiplicative => health.maximum() * amount,
669 };
670 let damage_contributor = by.and_then(|uid| {
671 read_data.id_maps.uid_entity(uid).map(|entity| {
672 DamageContributor::new(uid, read_data.groups.get(entity).cloned())
673 })
674 });
675 server_emitter.emit(HealthChangeEvent {
676 entity,
677 change: HealthChange {
678 amount,
679 by: damage_contributor,
680 cause,
681 time: *read_data.time,
682 precise: false,
683 instance: *instance,
684 },
685 });
686 };
687 },
688 BuffEffect::EnergyChangeOverTime {
689 rate,
690 kind,
691 tick_dur,
692 reset_rate_on_tick,
693 } => {
694 if let Some(num_ticks) = num_ticks(*tick_dur) {
695 let amount = *rate * num_ticks * tick_dur.0 as f32;
696
697 let amount = match *kind {
698 ModifierKind::Additive => amount,
699 ModifierKind::Multiplicative => energy.maximum() * amount,
700 };
701 server_emitter.emit(EnergyChangeEvent {
702 entity,
703 change: amount,
704 reset_rate: *reset_rate_on_tick,
705 });
706 };
707 },
708 BuffEffect::MaxHealthModifier { value, kind } => match kind {
709 ModifierKind::Additive => {
710 stat.max_health_modifiers.add_mod += *value;
711 },
712 ModifierKind::Multiplicative => {
713 stat.max_health_modifiers.mult_mod *= *value;
714 },
715 },
716 BuffEffect::MaxEnergyModifier { value, kind } => match kind {
717 ModifierKind::Additive => {
718 stat.max_energy_modifiers.add_mod += *value;
719 },
720 ModifierKind::Multiplicative => {
721 stat.max_energy_modifiers.mult_mod *= *value;
722 },
723 },
724 BuffEffect::DamageReduction(dr) => {
725 if *dr > 0.0 {
726 stat.damage_reduction.pos_mod = stat.damage_reduction.pos_mod.max(*dr);
727 } else {
728 stat.damage_reduction.neg_mod += dr;
729 }
730 },
731 BuffEffect::MaxHealthChangeOverTime {
732 rate,
733 kind,
734 target_fraction,
735 } => {
736 let potential_amount = (time.0 - buff_kind_start_time.0) as f32 * rate;
737
738 let potential_fraction = 1.0
740 + match kind {
741 ModifierKind::Additive => {
742 potential_amount / health.base_max()
745 },
746 ModifierKind::Multiplicative => {
747 potential_amount
749 },
750 };
751
752 let progress = if (1.0 - *target_fraction).abs() > f32::EPSILON {
756 (1.0 - potential_fraction) / (1.0 - *target_fraction)
757 } else {
758 1.0
759 };
760
761 let achieved_fraction = if progress > 1.0 {
764 *target_fraction
768 } else {
769 potential_fraction
771 };
772
773 stat.max_health_modifiers.mult_mod *= achieved_fraction;
775 },
776 BuffEffect::MovementSpeed(speed) => {
777 stat.move_speed_modifier *= *speed;
778 },
779 BuffEffect::AttackSpeed(speed) => {
780 stat.attack_speed_modifier *= *speed;
781 },
782 BuffEffect::RecoverySpeed(speed) => {
783 stat.recovery_speed_modifier *= *speed;
784 },
785 BuffEffect::GroundFriction(gf) => {
786 stat.friction_modifier *= *gf;
787 },
788 BuffEffect::PoiseReduction(pr) => {
789 if *pr > 0.0 {
790 stat.poise_reduction.pos_mod = stat.poise_reduction.pos_mod.max(*pr);
791 } else {
792 stat.poise_reduction.neg_mod += pr;
793 }
794 },
795 BuffEffect::PoiseDamageFromLostHealth(strength) => {
796 stat.poise_damage_modifier *= 1.0 + (1.0 - health.fraction()) * *strength;
797 },
798 BuffEffect::AttackDamage(dam) => {
799 stat.attack_damage_modifier *= *dam;
800 },
801 BuffEffect::PrecisionOverride(val) => {
802 stat.precision_multiplier_override = stat
804 .precision_multiplier_override
805 .map(|mult| mult.min(*val))
806 .or(Some(*val));
807 },
808 BuffEffect::PrecisionVulnerabilityOverride(val) => {
809 stat.precision_vulnerability_multiplier_override = stat
811 .precision_vulnerability_multiplier_override
812 .map(|mult| mult.max(*val))
813 .or(Some(*val));
814 },
815 BuffEffect::BodyChange(b) => {
816 if Some(current_body) != body_override.as_ref() {
823 *body_override = Some(*b)
824 }
825 },
826 BuffEffect::BuffImmunity(buff_kind) => {
827 if buffs_comp.contains(*buff_kind) {
828 server_emitter.emit(BuffEvent {
829 entity,
830 buff_change: BuffChange::RemoveByKind(*buff_kind),
831 });
832 }
833 },
834 BuffEffect::SwimSpeed(speed) => {
835 stat.swim_speed_modifier *= speed;
836 },
837 BuffEffect::AttackEffect(effect) => stat.effects_on_attack.push(effect.clone()),
838 BuffEffect::AttackPoise(p) => {
839 stat.poise_damage_modifier *= p;
840 },
841 BuffEffect::MitigationsPenetration(mp) => {
842 stat.mitigations_penetration =
843 1.0 - ((1.0 - stat.mitigations_penetration) * (1.0 - *mp));
844 },
845 BuffEffect::EnergyReward(er) => {
846 stat.energy_reward_modifier *= er;
847 },
848 BuffEffect::DamagedEffect(effect) => stat.effects_on_damaged.push(effect.clone()),
849 BuffEffect::DeathEffect(effect) => stat.effects_on_death.push(effect.clone()),
850 BuffEffect::DisableAuxiliaryAbilities => stat.disable_auxiliary_abilities = true,
851 BuffEffect::CrowdControlResistance(ccr) => {
852 stat.crowd_control_resistance += ccr;
853 },
854 BuffEffect::ItemEffectReduction(ier) => {
855 stat.item_effect_reduction *= 1.0 - ier;
856 },
857 };
858}