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