veloren/common/src/sys/buff.rs

88 lines
4.0 KiB
Rust
Raw Normal View History

use crate::{
comp::{BuffChange, BuffEffect, BuffId, Buffs, HealthChange, HealthSource, Stats},
state::DeltaTime,
};
use specs::{Entities, Join, Read, System, WriteStorage};
use std::time::Duration;
/// This system modifies entity stats, changing them using buffs
/// Currently, the system is VERY, VERY CRUDE and SYNC UN-FRIENDLY.
/// It does not use events and uses `Vec`s stored in component.
///
/// TODO: Make this production-quality system/design
pub struct Sys;
impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)]
type SystemData = (
Entities<'a>,
Read<'a, DeltaTime>,
WriteStorage<'a, Stats>,
WriteStorage<'a, Buffs>,
);
fn run(&mut self, (entities, dt, mut stats, mut buffs): Self::SystemData) {
for (entity, mut buffs) in (&entities, &mut buffs.restrict_mut()).join() {
let buff_comp = buffs.get_mut_unchecked();
let mut buff_indices_for_removal = Vec::new();
// Tick all de/buffs on a Buffs component.
for i in 0..buff_comp.buffs.len() {
// First, tick the buff and subtract delta from it
// and return how much "real" time the buff took (for tick independence).
// TODO: handle delta for "indefinite" buffs, i.e. time since they got removed.
let buff_delta = if let Some(remaining_time) = &mut buff_comp.buffs[i].time {
let pre_tick = remaining_time.as_secs_f32();
let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0));
let post_tick = if let Some(dur) = new_duration {
// The buff still continues.
*remaining_time -= Duration::from_secs_f32(dt.0);
dur.as_secs_f32()
} else {
// The buff has expired.
// Remove it.
buff_indices_for_removal.push(i);
0.0
};
pre_tick - post_tick
} else {
// The buff is indefinite, and it takes full tick (delta).
// TODO: Delta for indefinite buffs might be shorter since they can get removed
// *during a tick* and this treats it as it always happens on a *tick end*.
dt.0
};
// Now, execute the buff, based on it's delta
for effect in &mut buff_comp.buffs[i].effects {
match effect {
// Only add an effect here if it is continuous or it is not immediate
BuffEffect::HealthChangeOverTime { rate, accumulated } => {
*accumulated += *rate * buff_delta;
// Apply only 0.5 or higher damage
if accumulated.abs() > 5.0 {
if let Some(stats) = stats.get_mut(entity) {
let change = HealthChange {
amount: *accumulated as i32,
cause: HealthSource::Unknown,
};
stats.health.change_by(change);
}
*accumulated = 0.0;
};
},
_ => {},
};
}
}
// Remove buffs that have expired.
// Since buffs are added into this vec as it iterates up through the list, it
// will be in order of increasing values. Therefore to avoid
// removing the incorrect buff, removal will start from the greatest index
// value, which is the last in this vec.
while !buff_indices_for_removal.is_empty() {
if let Some(i) = buff_indices_for_removal.pop() {
buff_comp.buffs.remove(i);
}
}
}
}
}