diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 1083504ad2..ee351db610 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -22,4 +22,5 @@ pub use inputs::Jumping; pub use inputs::Respawning; pub use player::Player; pub use stats::Dying; +pub use stats::HealthSource; pub use stats::Stats; diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 08a0559f4d..67e06674fd 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -1,17 +1,33 @@ -use crate::state::Time; +use crate::state::{Time, Uid}; use specs::{Component, FlaggedStorage, NullStorage, VecStorage}; +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub enum HealthSource { + Attack { by: Uid }, // TODO: Implement weapon + Suicide, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Health { - pub current: u32, - pub maximum: u32, - pub last_change: Option<(i32, f64)>, + current: u32, + maximum: u32, + pub last_change: Option<(i32, f64, HealthSource)>, } impl Health { - pub fn change_by(&mut self, amount: i32) { + pub fn get_current(&self) -> u32 { + self.current + } + pub fn get_maximum(&self) -> u32 { + self.maximum + } + pub fn set_to(&mut self, amount: u32, cause: HealthSource) { + self.last_change = Some((amount as i32 - self.current as i32, 0.0, cause)); + self.current = amount; + } + pub fn change_by(&mut self, amount: i32, cause: HealthSource) { self.current = (self.current as i32 + amount).max(0) as u32; - self.last_change = Some((amount, 0.0)); + self.last_change = Some((amount, 0.0, cause)); } } @@ -47,9 +63,11 @@ impl Component for Stats { type Storage = FlaggedStorage>; } -#[derive(Copy, Clone, Debug, Default, Serialize, Deserialize)] -pub struct Dying; +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct Dying { + pub cause: HealthSource, +} impl Component for Dying { - type Storage = NullStorage; + type Storage = VecStorage; } diff --git a/common/src/sys/inputs.rs b/common/src/sys/inputs.rs index 0ed148a774..1d336c74d3 100644 --- a/common/src/sys/inputs.rs +++ b/common/src/sys/inputs.rs @@ -6,9 +6,10 @@ use vek::*; use crate::{ comp::{ phys::{Dir, ForceUpdate, Pos, Vel}, - Animation, AnimationInfo, Attacking, Control, Gliding, Jumping, Respawning, Stats, + Animation, AnimationInfo, Attacking, Control, Gliding, HealthSource, Jumping, Respawning, + Stats, }, - state::{DeltaTime, Time}, + state::{DeltaTime, Time, Uid}, terrain::TerrainMap, vol::{ReadVol, Vox}, }; @@ -19,6 +20,7 @@ pub struct Sys; impl<'a> System<'a> for Sys { type SystemData = ( Entities<'a>, + ReadStorage<'a, Uid>, Read<'a, Time>, Read<'a, DeltaTime>, ReadExpect<'a, TerrainMap>, @@ -39,6 +41,7 @@ impl<'a> System<'a> for Sys { &mut self, ( entities, + uids, time, dt, terrain, @@ -140,8 +143,8 @@ impl<'a> System<'a> for Sys { ); } - for (entity, pos, dir, attacking) in - (&entities, &positions, &directions, &mut attackings).join() + for (entity, &uid, pos, dir, attacking) in + (&entities, &uids, &positions, &directions, &mut attackings).join() { if !attacking.applied { for (b, pos_b, mut stat_b, mut vel_b) in @@ -154,7 +157,7 @@ impl<'a> System<'a> for Sys { && dir.0.angle_between(pos_b.0 - pos.0).to_degrees() < 70.0 { // Deal damage - stat_b.hp.change_by(-10); // TODO: variable damage + stat_b.hp.change_by(-10, HealthSource::Attack { by: uid }); // TODO: variable damage and weapon vel_b.0 += (pos_b.0 - pos.0).normalized() * 10.0; vel_b.0.z = 15.0; force_updates.insert(b, ForceUpdate); diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index 5a37c23401..f1834d9d0c 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -23,7 +23,16 @@ impl<'a> System<'a> for Sys { for (entity, mut stat) in (&entities, &mut stats).join() { if stat.should_die() && !stat.is_dead { // TODO: Replace is_dead with client states - dyings.insert(entity, Dying); + dyings.insert( + entity, + Dying { + cause: stat + .hp + .last_change + .expect("Nothing caused the entity to die") + .2, // Safe because damage is necessary for death + }, + ); stat.is_dead = true; } if let Some(change) = &mut stat.hp.last_change { diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 74c975eccb..7b685be406 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -150,7 +150,7 @@ fn handle_kill(server: &mut Server, entity: EcsEntity, args: String, action: &Ch .ecs_mut() .write_storage::() .get_mut(entity) - .map(|s| s.hp.current = 0); + .map(|s| s.hp.set_to(0, comp::HealthSource::Suicide)); } fn handle_alias(server: &mut Server, entity: EcsEntity, args: String, action: &ChatCommand) { diff --git a/server/src/lib.rs b/server/src/lib.rs index 1f7205c7dc..d92f015545 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -211,14 +211,35 @@ impl Server { self.world.tick(dt); // Sync deaths. - let todo_kill = ( - &self.state.ecs().entities(), - &self.state.ecs().read_storage::(), - ) + let ecs = &self.state.ecs(); + let clients = &mut self.clients; + let todo_kill = (&ecs.entities(), &ecs.read_storage::()) .join() - .map(|(entity, _)| entity) + .map(|(entity, dying)| { + // Chat message + if let Some(player) = ecs.read_storage::().get(entity) { + // While waiting for if-let-chains to be implemented... + let msg = if let comp::HealthSource::Attack { by } = dying.cause { + if let Some(attacker) = ecs + .read_storage::() + .get(ecs.entity_from_uid(by.into()).unwrap()) + { + format!("{} was killed by {}", &player.alias, &attacker.alias) + } else { + format!("{} died", &player.alias) + } + } else { + format!("{} died", &player.alias) + }; + + clients.notify_registered(ServerMsg::Chat(msg)); + } + + entity + }) .collect::>(); + // Actually kill them for entity in todo_kill { if let Some(client) = self.clients.get_mut(&entity) { self.state @@ -229,11 +250,6 @@ impl Server { self.state.ecs_mut().delete_entity_synced(entity); continue; } - - if let Some(player) = self.state.ecs().read_storage::().get(entity) { - self.clients - .notify_registered(ServerMsg::Chat(format!("{} died", &player.alias))); - } } // Handle respawns diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 5cacd0bb26..4efbdaf41a 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -358,7 +358,7 @@ impl Hud { // Filling Rectangle::fill_with( [ - 120.0 * (stats.hp.current as f64 / stats.hp.maximum as f64), + 120.0 * (stats.hp.get_current() as f64 / stats.hp.get_maximum() as f64), 8.0, ], HP_COLOR, diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index 7e2fc45207..91354370e6 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -78,7 +78,7 @@ impl<'a> Widget for Skillbar<'a> { let next_level_xp = (level as f64).powi(4) - start_level_xp; // TODO: We need a max xp value let xp_percentage = (self.stats.xp as f64 - start_level_xp) / next_level_xp; - let hp_percentage = self.stats.hp.current as f64 / self.stats.hp.maximum as f64; + let hp_percentage = self.stats.hp.get_current() as f64 / self.stats.hp.get_maximum() as f64; let mana_percentage = 1.0; // TODO: Only show while aiming with a bow or when casting a spell. diff --git a/voxygen/src/scene/figure.rs b/voxygen/src/scene/figure.rs index cb67afaa33..f3f2be094c 100644 --- a/voxygen/src/scene/figure.rs +++ b/voxygen/src/scene/figure.rs @@ -475,7 +475,7 @@ impl FigureMgr { // Change in health as color! let col = stats .and_then(|stats| stats.hp.last_change) - .map(|(change_by, time)| { + .map(|(change_by, time, _)| { Rgba::broadcast(1.0) + Rgba::new(0.0, -1.0, -1.0, 0.0) .map(|c| (c / (1.0 + DAMAGE_FADE_COEFFICIENT * time)) as f32)