Files
veloren/voxygen/src/ecs/sys/floater.rs

201 lines
7.5 KiB
Rust

use crate::ecs::{
comp::{HpFloater, HpFloaterList},
ExpFloater, MyEntity, MyExpFloaterList,
};
use common::{
comp::{HealthSource, Pos, Stats},
state::DeltaTime,
sync::Uid,
};
use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteStorage};
// How long floaters last (in seconds)
pub const HP_SHOWTIME: f32 = 3.0;
pub const MY_HP_SHOWTIME: f32 = 2.5;
pub const MY_EXP_SHOWTIME: f32 = 4.0;
pub struct Sys;
impl<'a> System<'a> for Sys {
#[allow(clippy::type_complexity)] // TODO: Pending review in #587
type SystemData = (
Entities<'a>,
ReadExpect<'a, MyEntity>,
Read<'a, DeltaTime>,
Write<'a, MyExpFloaterList>,
ReadStorage<'a, Uid>,
ReadStorage<'a, Pos>,
ReadStorage<'a, Stats>,
WriteStorage<'a, HpFloaterList>,
);
#[allow(clippy::blocks_in_if_conditions)] // TODO: Pending review in #587
fn run(
&mut self,
(entities, my_entity, dt, mut my_exp_floater_list, uids, pos, stats, mut hp_floater_lists): Self::SystemData,
) {
// Add hp floater lists to all entities with stats and a position
// Note: necessary in order to know last_hp
for (entity, last_hp) in (&entities, &stats, &pos, !&hp_floater_lists)
.join()
.map(|(e, s, _, _)| (e, s.health.current()))
.collect::<Vec<_>>()
{
let _ = hp_floater_lists.insert(entity, HpFloaterList {
floaters: Vec::new(),
last_hp,
time_since_last_dmg_by_me: None,
});
}
// Add hp floaters to all entities that have been damaged
let my_uid = uids.get(my_entity.0);
for (entity, health, hp_floater_list) in (&entities, &stats, &mut hp_floater_lists)
.join()
.map(|(e, s, fl)| (e, s.health, fl))
{
// Increment timer for time since last damaged by me
hp_floater_list
.time_since_last_dmg_by_me
.as_mut()
.map(|t| *t += dt.0);
// Check if health has changed (won't work if damaged and then healed with
// equivalently in the same frame)
if hp_floater_list.last_hp != health.current() {
hp_floater_list.last_hp = health.current();
// TODO: What if multiple health changes occurred since last check here
// Also, If we make stats store a vec of the last_changes (from say the last
// frame), what if the client receives the stats component from
// two different server ticks at once, then one will be lost
// (tbf this is probably a rare occurance and the results
// would just be a transient glitch in the display of these damage numbers)
// (maybe health changes could be sent to the client as a list
// of events)
if match health.last_change.1.cause {
HealthSource::Attack { by }
| HealthSource::Projectile { owner: Some(by) }
| HealthSource::Energy { owner: Some(by) }
| HealthSource::Explosion { owner: Some(by) }
| HealthSource::Buff { owner: Some(by) }
| HealthSource::Healing { by: Some(by) } => {
let by_me = my_uid.map_or(false, |&uid| by == uid);
// If the attack was by me also reset this timer
if by_me {
hp_floater_list.time_since_last_dmg_by_me = Some(0.0);
}
my_entity.0 == entity || by_me
},
HealthSource::Suicide => my_entity.0 == entity,
HealthSource::World => my_entity.0 == entity,
HealthSource::LevelUp => my_entity.0 == entity,
HealthSource::Command => true,
HealthSource::Item => true,
_ => false,
} {
hp_floater_list.floaters.push(HpFloater {
timer: 0.0,
hp_change: health.last_change.1.amount,
rand: rand::random(),
});
}
}
}
// Remove floater lists on entities without stats or without position
for entity in (&entities, !&stats, &hp_floater_lists)
.join()
.map(|(e, _, _)| e)
.collect::<Vec<_>>()
{
hp_floater_lists.remove(entity);
}
for entity in (&entities, !&pos, &hp_floater_lists)
.join()
.map(|(e, _, _)| e)
.collect::<Vec<_>>()
{
hp_floater_lists.remove(entity);
}
// Maintain existing floaters
for (
entity,
HpFloaterList {
ref mut floaters,
ref last_hp,
..
},
) in (&entities, &mut hp_floater_lists).join()
{
for mut floater in floaters.iter_mut() {
// Increment timer
floater.timer += dt.0;
}
// Clear floaters if newest floater is past show time or health runs out
if floaters.last().map_or(false, |f| {
f.timer
> if entity != my_entity.0 {
HP_SHOWTIME
} else {
MY_HP_SHOWTIME
}
|| *last_hp == 0
}) {
floaters.clear();
}
}
// Update MyExpFloaterList
if let Some(stats) = stats.get(my_entity.0) {
let mut fl = my_exp_floater_list;
// Add a floater if exp changed
// TODO: can't handle if you level up more than once (maybe store total exp in
// stats)
let exp_change = if stats.level.level() != fl.last_level {
if stats.level.level() > fl.last_level {
stats.exp.current() as i32 + fl.last_exp_max as i32 - fl.last_exp as i32
} else {
// Level down
stats.exp.current() as i32 - stats.exp.maximum() as i32 - fl.last_exp as i32
}
} else {
stats.exp.current() as i32 - fl.last_exp as i32
};
if exp_change != 0 {
fl.floaters.push(ExpFloater {
timer: 0.0,
exp_change,
rand: (rand::random(), rand::random()),
});
}
// Increment timers
for mut floater in &mut fl.floaters {
floater.timer += dt.0;
}
// Clear if the newest is past show time
if fl
.floaters
.last()
.map_or(false, |f| f.timer > MY_EXP_SHOWTIME)
{
fl.floaters.clear();
}
// Update stored values
fl.last_exp = stats.exp.current();
fl.last_exp_max = stats.exp.maximum();
fl.last_level = stats.level.level();
} else {
// Clear if stats component doesn't exist
my_exp_floater_list.floaters.clear();
// Clear stored values
my_exp_floater_list.last_exp = 0;
my_exp_floater_list.last_exp_max = 0;
my_exp_floater_list.last_level = 0;
}
}
}