From e0cbbf52ed9422cff7099dec4cb4b092ce896668 Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Oct 2020 15:41:21 -0500 Subject: [PATCH 1/9] Changed explosions to take a vec of RadiusEffects. Changed Damage to a struct of DamageSource and value. Added interpolation function to damage. --- common/src/combat.rs | 57 +++-- common/src/comp/inventory/item/tool.rs | 66 ++++-- common/src/event.rs | 1 - common/src/explosion.rs | 13 +- common/src/lib.rs | 4 +- common/src/states/basic_beam.rs | 16 +- common/src/states/basic_melee.rs | 7 +- common/src/states/charged_melee.rs | 15 +- common/src/states/charged_ranged.rs | 16 +- common/src/states/combo_melee.rs | 10 +- common/src/states/dash_melee.rs | 16 +- common/src/states/leap_melee.rs | 7 +- common/src/states/shockwave.rs | 7 +- common/src/states/spin_melee.rs | 7 +- common/src/sys/beam.rs | 4 +- common/src/sys/projectile.rs | 2 - server/src/cmd.rs | 21 +- server/src/events/entity_manipulation.rs | 266 ++++++++++++----------- server/src/events/mod.rs | 3 +- server/src/sys/object.rs | 40 ++-- 20 files changed, 336 insertions(+), 242 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index bebda0aa11..ed11e2fb5e 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -24,17 +24,28 @@ impl Damages { pub fn get_damage(self, same_group: bool) -> Option { if same_group { self.group } else { self.enemy } } + + pub fn contains_damage(self, source: DamageSource) -> bool { + self.enemy.map_or(false, |e| e.source == source) + || self.group.map_or(false, |g| g.source == source) + } +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum DamageSource { + Melee, + Healing, + Projectile, + Explosion, + Falling, + Shockwave, + Energy, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub enum Damage { - Melee(f32), - Healing(f32), - Projectile(f32), - Explosion(f32), - Falling(f32), - Shockwave(f32), - Energy(f32), +pub struct Damage { + pub source: DamageSource, + pub value: f32, } impl Damage { @@ -44,9 +55,9 @@ impl Damage { loadout: Option<&Loadout>, uid: Option, ) -> HealthChange { - match self { - Damage::Melee(damage) => { - let mut damage = damage; + let mut damage = self.value; + match self.source { + DamageSource::Melee => { // Critical hit let mut critdamage = 0.0; if rand::random() { @@ -70,8 +81,7 @@ impl Damage { cause: HealthSource::Attack { by: uid.unwrap() }, } }, - Damage::Projectile(damage) => { - let mut damage = damage; + DamageSource::Projectile => { // Critical hit if rand::random() { damage *= 1.2; @@ -89,8 +99,7 @@ impl Damage { cause: HealthSource::Projectile { owner: uid }, } }, - Damage::Explosion(damage) => { - let mut damage = damage; + DamageSource::Explosion => { // Block if block { damage *= 1.0 - BLOCK_EFFICIENCY @@ -104,8 +113,7 @@ impl Damage { cause: HealthSource::Explosion { owner: uid }, } }, - Damage::Shockwave(damage) => { - let mut damage = damage; + DamageSource::Shockwave => { // Armor let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); damage *= 1.0 - damage_reduction; @@ -115,8 +123,7 @@ impl Damage { cause: HealthSource::Attack { by: uid.unwrap() }, } }, - Damage::Energy(damage) => { - let mut damage = damage; + DamageSource::Energy => { // Armor let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); damage *= 1.0 - damage_reduction; @@ -126,12 +133,11 @@ impl Damage { cause: HealthSource::Energy { owner: uid }, } }, - Damage::Healing(heal) => HealthChange { - amount: heal as i32, + DamageSource::Healing => HealthChange { + amount: damage as i32, cause: HealthSource::Healing { by: uid }, }, - Damage::Falling(damage) => { - let mut damage = damage; + DamageSource::Falling => { // Armor let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); if (damage_reduction - 1.0).abs() < f32::EPSILON { @@ -144,6 +150,11 @@ impl Damage { }, } } + + pub fn interpolate_damage(&mut self, frac: f32, min: f32) { + let new_damage = min + frac * (self.value - min); + self.value = new_damage; + } } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 7f8b5499d8..9dac188ada 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -8,7 +8,7 @@ use crate::{ projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile, }, states::combo_melee, - Damage, Damages, Explosion, Knockback, + Damage, DamageSource, Damages, Explosion, Knockback, RadiusEffect, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -303,7 +303,10 @@ impl Tool { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ projectile::Effect::Damages(Damages::new( - Some(Damage::Projectile(40.0 * self.base_power())), + Some(Damage { + source: DamageSource::Projectile, + value: 40.0 * self.base_power(), + }), None, )), projectile::Effect::Knockback(Knockback::Away(10.0)), @@ -358,7 +361,10 @@ impl Tool { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ projectile::Effect::Damages(Damages::new( - Some(Damage::Projectile(40.0 * self.base_power())), + Some(Damage { + source: DamageSource::Projectile, + value: 40.0 * self.base_power(), + }), None, )), projectile::Effect::Knockback(Knockback::Away(10.0)), @@ -419,24 +425,34 @@ impl Tool { projectile: Projectile { hit_solid: vec![ projectile::Effect::Explode(Explosion { + effects: vec![RadiusEffect::Damages(Damages::new( + Some(Damage { + source: DamageSource::Explosion, + value: 50.0 * self.base_power(), + }), + Some(Damage { + source: DamageSource::Healing, + value: 140.0 * self.base_power(), + }), + ))], radius: 3.0 + 2.5 * self.base_power(), - max_damage: (50.0 * self.base_power()) as u32, - min_damage: (20.0 * self.base_power()) as u32, - max_heal: (140.0 * self.base_power()) as u32, - min_heal: (50.0 * self.base_power()) as u32, - terrain_destruction_power: 0.0, energy_regen: 0, }), projectile::Effect::Vanish, ], hit_entity: vec![ projectile::Effect::Explode(Explosion { + effects: vec![RadiusEffect::Damages(Damages::new( + Some(Damage { + source: DamageSource::Explosion, + value: 50.0 * self.base_power(), + }), + Some(Damage { + source: DamageSource::Healing, + value: 140.0 * self.base_power(), + }), + ))], radius: 3.0 + 2.5 * self.base_power(), - max_damage: (50.0 * self.base_power()) as u32, - min_damage: (20.0 * self.base_power()) as u32, - max_heal: (140.0 * self.base_power()) as u32, - min_heal: (50.0 * self.base_power()) as u32, - terrain_destruction_power: 0.0, energy_regen: 0, }), projectile::Effect::Vanish, @@ -462,24 +478,28 @@ impl Tool { projectile: Projectile { hit_solid: vec![ projectile::Effect::Explode(Explosion { + effects: vec![RadiusEffect::Damages(Damages::new( + Some(Damage { + source: DamageSource::Explosion, + value: 100.0 * self.base_power(), + }), + None, + ))], radius: 5.0, - max_damage: (100.0 * self.base_power()) as u32, - min_damage: 0, - max_heal: 0, - min_heal: 0, - terrain_destruction_power: 0.0, energy_regen: 50, }), projectile::Effect::Vanish, ], hit_entity: vec![ projectile::Effect::Explode(Explosion { + effects: vec![RadiusEffect::Damages(Damages::new( + Some(Damage { + source: DamageSource::Explosion, + value: 100.0 * self.base_power(), + }), + None, + ))], radius: 5.0, - max_damage: (100.0 * self.base_power()) as u32, - min_damage: 0, - max_heal: 0, - min_heal: 0, - terrain_destruction_power: 0.0, energy_regen: 50, }), projectile::Effect::Vanish, diff --git a/common/src/event.rs b/common/src/event.rs index a51620c961..9dc0d725de 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -31,7 +31,6 @@ pub enum ServerEvent { pos: Vec3, explosion: Explosion, owner: Option, - friendly_damage: bool, reagent: Option, }, Damage { diff --git a/common/src/explosion.rs b/common/src/explosion.rs index 2779f50534..941d534c59 100644 --- a/common/src/explosion.rs +++ b/common/src/explosion.rs @@ -1,12 +1,15 @@ +use crate::combat::Damages; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Explosion { + pub effects: Vec, pub radius: f32, - pub max_damage: u32, - pub min_damage: u32, - pub max_heal: u32, - pub min_heal: u32, - pub terrain_destruction_power: f32, pub energy_regen: u32, } + +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] +pub enum RadiusEffect { + Damages(Damages), + TerrainDestruction(f32), +} diff --git a/common/src/lib.rs b/common/src/lib.rs index b35122fa67..1959d85135 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -53,6 +53,6 @@ pub mod util; pub mod vol; pub mod volumes; -pub use combat::{Damage, Damages, Knockback}; -pub use explosion::Explosion; +pub use combat::{Damage, DamageSource, Damages, Knockback}; +pub use explosion::{Explosion, RadiusEffect}; pub use loadout_builder::LoadoutBuilder; diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index 4a387ba7da..12ff5fd381 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -4,7 +4,7 @@ use crate::{ states::utils::*, sync::Uid, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, Damages, + Damage, DamageSource, Damages, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -107,12 +107,14 @@ impl CharacterBehavior for Data { if ability_key_is_pressed(data, self.static_data.ability_key) && (self.static_data.energy_drain == 0 || update.energy.current() > 0) { - let damage = Damage::Energy( - self.static_data.base_dps as f32 / self.static_data.tick_rate, - ); - let heal = Damage::Healing( - self.static_data.base_hps as f32 / self.static_data.tick_rate, - ); + let damage = Damage { + source: DamageSource::Energy, + value: self.static_data.base_dps as f32 / self.static_data.tick_rate, + }; + let heal = Damage { + source: DamageSource::Healing, + value: self.static_data.base_hps as f32 / self.static_data.tick_rate, + }; let energy_regen = (self.static_data.energy_regen as f32 / self.static_data.tick_rate) as u32; let energy_cost = diff --git a/common/src/states/basic_melee.rs b/common/src/states/basic_melee.rs index 228a10a204..ca4e002cdd 100644 --- a/common/src/states/basic_melee.rs +++ b/common/src/states/basic_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -77,7 +77,10 @@ impl CharacterBehavior for Data { // Hit attempt data.updater.insert(data.entity, Attacking { damages: Damages::new( - Some(Damage::Melee(self.static_data.base_damage as f32)), + Some(Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32, + }), None, ), range: self.static_data.range, diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index e0255e89a4..deec9de03c 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::{StageSection, *}, sys::character_behavior::*, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -109,16 +109,21 @@ impl CharacterBehavior for Data { }, StageSection::Swing => { if !self.exhausted { - let damage = self.static_data.initial_damage as f32 - + (self.static_data.max_damage - self.static_data.initial_damage) as f32 - * self.charge_amount; + let mut damage = Damage { + source: DamageSource::Melee, + value: self.static_data.max_damage as f32, + }; + damage.interpolate_damage( + self.charge_amount, + self.static_data.initial_damage as f32, + ); let knockback = self.static_data.initial_knockback + (self.static_data.max_knockback - self.static_data.initial_knockback) * self.charge_amount; // Hit attempt data.updater.insert(data.entity, Attacking { - damages: Damages::new(Some(Damage::Melee(damage)), None), + damages: Damages::new(Some(damage), None), range: self.static_data.range, max_angle: self.static_data.max_angle.to_radians(), applied: false, diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index 32d1796527..e6141cfe44 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -7,7 +7,7 @@ use crate::{ event::ServerEvent, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -84,10 +84,11 @@ impl CharacterBehavior for Data { let charge_frac = (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()) .min(1.0); - let damage = self.static_data.initial_damage as f32 - + (charge_frac - * (self.static_data.max_damage - self.static_data.initial_damage) - as f32); + let mut damage = Damage { + source: DamageSource::Projectile, + value: self.static_data.max_damage as f32, + }; + damage.interpolate_damage(charge_frac, self.static_data.initial_damage as f32); let knockback = self.static_data.initial_knockback as f32 + (charge_frac * (self.static_data.max_knockback - self.static_data.initial_knockback) @@ -96,10 +97,7 @@ impl CharacterBehavior for Data { let mut projectile = Projectile { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ - projectile::Effect::Damages(Damages::new( - Some(Damage::Projectile(damage)), - None, - )), + projectile::Effect::Damages(Damages::new(Some(damage), None)), projectile::Effect::Knockback(Knockback::Away(knockback)), projectile::Effect::Vanish, projectile::Effect::Buff { diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index 285804e278..4fe358c1b4 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -127,7 +127,13 @@ impl CharacterBehavior for Data { * self.static_data.stage_data[stage_index].damage_increase, ); data.updater.insert(data.entity, Attacking { - damages: Damages::new(Some(Damage::Melee(damage as f32)), None), + damages: Damages::new( + Some(Damage { + source: DamageSource::Melee, + value: damage as f32, + }), + None, + ), range: self.static_data.stage_data[stage_index].range, max_angle: self.static_data.stage_data[stage_index].angle.to_radians(), applied: false, diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 8a99b80bd9..3da10c386c 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -122,16 +122,20 @@ impl CharacterBehavior for Data { let charge_frac = (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()) .min(1.0); - let damage = (self.static_data.max_damage as f32 - - self.static_data.base_damage as f32) - * charge_frac - + self.static_data.base_damage as f32; + let mut damage = Damage { + source: DamageSource::Melee, + value: self.static_data.max_damage as f32, + }; + damage.interpolate_damage( + charge_frac, + self.static_data.base_damage as f32, + ); let knockback = (self.static_data.max_knockback - self.static_data.base_knockback) * charge_frac + self.static_data.base_knockback; data.updater.insert(data.entity, Attacking { - damages: Damages::new(Some(Damage::Melee(damage)), None), + damages: Damages::new(Some(damage), None), range: self.static_data.range, max_angle: self.static_data.angle.to_radians(), applied: false, diff --git a/common/src/states/leap_melee.rs b/common/src/states/leap_melee.rs index 0739761281..886375c76e 100644 --- a/common/src/states/leap_melee.rs +++ b/common/src/states/leap_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, StateUpdate}, states::utils::{StageSection, *}, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -134,7 +134,10 @@ impl CharacterBehavior for Data { // Hit attempt, when animation plays data.updater.insert(data.entity, Attacking { damages: Damages::new( - Some(Damage::Melee(self.static_data.base_damage as f32)), + Some(Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32, + }), None, ), range: self.static_data.range, diff --git a/common/src/states/shockwave.rs b/common/src/states/shockwave.rs index de0cbf3ab5..0141f0112a 100644 --- a/common/src/states/shockwave.rs +++ b/common/src/states/shockwave.rs @@ -3,7 +3,7 @@ use crate::{ event::ServerEvent, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -71,7 +71,10 @@ impl CharacterBehavior for Data { speed: self.static_data.shockwave_speed, duration: self.static_data.shockwave_duration, damages: Damages::new( - Some(Damage::Shockwave(self.static_data.damage as f32)), + Some(Damage { + source: DamageSource::Shockwave, + value: self.static_data.damage as f32, + }), None, ), knockback: self.static_data.knockback, diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 14c4bec054..746e2bf58a 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -5,7 +5,7 @@ use crate::{ character_behavior::{CharacterBehavior, JoinData}, phys::GRAVITY, }, - Damage, Damages, Knockback, + Damage, DamageSource, Damages, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -105,7 +105,10 @@ impl CharacterBehavior for Data { // Hit attempt data.updater.insert(data.entity, Attacking { damages: Damages::new( - Some(Damage::Melee(self.static_data.base_damage as f32)), + Some(Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32, + }), None, ), range: self.static_data.range, diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index 8b6807b1dc..3cf769a9a5 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -6,7 +6,7 @@ use crate::{ event::{EventBus, ServerEvent}, state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, - Damage, + DamageSource, }; use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; @@ -180,7 +180,7 @@ impl<'a> System<'a> for Sys { let change = damage.modify_damage(block, loadouts.get(b), beam_segment.owner); - if !matches!(damage, Damage::Healing(_)) { + if !matches!(damage.source, DamageSource::Healing) { server_emitter.emit(ServerEvent::Damage { uid: *uid_b, change, diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index eae08e8055..a8dce0663e 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -142,7 +142,6 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: e, owner: projectile.owner, - friendly_damage: false, reagent: None, }) }, @@ -187,7 +186,6 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: e, owner: projectile.owner, - friendly_damage: false, reagent: None, }) }, diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 6050409002..2db160010e 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -18,7 +18,7 @@ use common::{ terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, util::Dir, vol::RectVolSize, - Explosion, LoadoutBuilder, + Damage, DamageSource, Damages, Explosion, LoadoutBuilder, RadiusEffect, }; use rand::Rng; use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; @@ -1153,16 +1153,23 @@ fn handle_explosion( .emit_now(ServerEvent::Explosion { pos: pos.0, explosion: Explosion { + effects: vec![ + RadiusEffect::Damages(Damages::new( + Some(Damage { + source: DamageSource::Explosion, + value: 100.0 * power, + }), + Some(Damage { + source: DamageSource::Explosion, + value: 100.0 * power, + }), + )), + RadiusEffect::TerrainDestruction(power), + ], radius: 3.0 * power, - max_damage: (100.0 * power) as u32, - min_damage: 0, - max_heal: 0, - min_heal: 0, - terrain_destruction_power: power, energy_regen: 0, }, owner: ecs.read_storage::().get(target).copied(), - friendly_damage: true, reagent: None, }) }, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 48ae6b7563..5c6c8d0ab9 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -18,7 +18,7 @@ use common::{ sys::melee::BLOCK_ANGLE, terrain::{Block, TerrainGrid}, vol::ReadVol, - Damage, Explosion, + Damage, DamageSource, Explosion, RadiusEffect, }; use comp::item::Reagent; use rand::prelude::*; @@ -454,7 +454,10 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) if vel.z <= -30.0 { if let Some(stats) = state.ecs().write_storage::().get_mut(entity) { let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0; - let damage = Damage::Falling(falldmg); + let damage = Damage { + source: DamageSource::Falling, + value: falldmg, + }; let loadouts = state.ecs().read_storage::(); let change = damage.modify_damage(false, loadouts.get(entity), None); stats.health.change_by(change); @@ -506,26 +509,35 @@ pub fn handle_explosion( pos: Vec3, explosion: Explosion, owner: Option, - friendly_damage: bool, reagent: Option, ) { // Go through all other entities let ecs = &server.state.ecs(); - let outcome_power = if explosion.max_heal > explosion.max_damage { - (-explosion.terrain_destruction_power).min(explosion.max_heal as f32 / -100.0) - } else { - explosion - .terrain_destruction_power - .max(explosion.max_damage as f32 / 100.0) - }; // Add an outcome + // Uses radius as outcome power, makes negative if explosion has healing effect + #[allow(clippy::blocks_in_if_conditions)] + let outcome_power = explosion.radius + * if explosion.effects.iter().any(|e| { + if let RadiusEffect::Damages(d) = e { + d.contains_damage(DamageSource::Healing) + } else { + false + } + }) { + -1.0 + } else { + 1.0 + }; ecs.write_resource::>() .push(Outcome::Explosion { pos, power: outcome_power, radius: explosion.radius, - is_attack: explosion.max_heal > 0 || explosion.max_damage > 0, + is_attack: explosion + .effects + .iter() + .any(|e| matches!(e, RadiusEffect::Damages(_))), reagent, }); let owner_entity = owner.and_then(|uid| { @@ -534,129 +546,133 @@ pub fn handle_explosion( }); let groups = ecs.read_storage::(); - for (entity_b, pos_b, ori_b, character_b, stats_b, loadout_b) in ( - &ecs.entities(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ecs.read_storage::().maybe(), - &mut ecs.write_storage::(), - ecs.read_storage::().maybe(), - ) - .join() - { - let distance_squared = pos.distance_squared(pos_b.0); - // Check if it is a hit - if !stats_b.is_dead - // RADIUS - && distance_squared < explosion.radius.powi(2) - { - // See if entities are in the same group - let mut same_group = owner_entity - .and_then(|e| groups.get(e)) - .map_or(false, |group_a| Some(group_a) == groups.get(entity_b)); - if let Some(entity) = owner_entity { - if entity == entity_b { - same_group = true; - } - } - // Don't heal if outside group - // Don't damage in the same group - let is_damage = (friendly_damage || !same_group) && explosion.max_damage > 0; - let is_heal = same_group && explosion.max_heal > 0 && !friendly_damage; - if !is_heal && !is_damage { - continue; - } - - let strength = 1.0 - distance_squared / explosion.radius.powi(2); - let damage = if is_heal { - Damage::Healing( - explosion.min_heal as f32 - + (explosion.max_heal - explosion.min_heal) as f32 * strength, + for effect in explosion.effects { + match effect { + RadiusEffect::Damages(damages) => { + for (entity_b, pos_b, ori_b, character_b, stats_b, loadout_b) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ecs.read_storage::().maybe(), + &mut ecs.write_storage::(), + ecs.read_storage::().maybe(), ) - } else { - Damage::Explosion( - explosion.min_damage as f32 - + (explosion.max_damage - explosion.min_damage) as f32 * strength, - ) - }; + .join() + { + let distance_squared = pos.distance_squared(pos_b.0); + // Check if it is a hit + if !stats_b.is_dead + // RADIUS + && distance_squared < explosion.radius.powi(2) + { + // See if entities are in the same group + let mut same_group = owner_entity + .and_then(|e| groups.get(e)) + .map_or(false, |group_a| Some(group_a) == groups.get(entity_b)); + if let Some(entity) = owner_entity { + if entity == entity_b { + same_group = true; + } + } - let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) - && ori_b.0.angle_between(pos - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; + let mut damage = if let Some(damage) = damages.get_damage(same_group) { + damage + } else { + continue; + }; - let change = damage.modify_damage(block, loadout_b, owner); + let strength = 1.0 - distance_squared / explosion.radius.powi(2); + damage.interpolate_damage(strength, 0.0); - if change.amount != 0 { - stats_b.health.change_by(change); - if let Some(owner) = owner_entity { - if let Some(energy) = ecs.write_storage::().get_mut(owner) { - energy - .change_by(explosion.energy_regen as i32, comp::EnergySource::HitEnemy); + let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) + && ori_b.0.angle_between(pos - pos_b.0) + < BLOCK_ANGLE.to_radians() / 2.0; + + let change = damage.modify_damage(block, loadout_b, owner); + + if change.amount != 0 { + stats_b.health.change_by(change); + if let Some(owner) = owner_entity { + if let Some(energy) = + ecs.write_storage::().get_mut(owner) + { + energy.change_by( + explosion.energy_regen as i32, + comp::EnergySource::HitEnemy, + ); + } + } + } } } - } - } - } + }, + RadiusEffect::TerrainDestruction(power) => { + const RAYS: usize = 500; - const RAYS: usize = 500; + // Color terrain + let mut touched_blocks = Vec::new(); + let color_range = power * 2.7; + for _ in 0..RAYS { + let dir = Vec3::new( + rand::random::() - 0.5, + rand::random::() - 0.5, + rand::random::() - 0.5, + ) + .normalized(); - // Color terrain - let mut touched_blocks = Vec::new(); - let color_range = explosion.terrain_destruction_power * 2.7; - for _ in 0..RAYS { - let dir = Vec3::new( - rand::random::() - 0.5, - rand::random::() - 0.5, - rand::random::() - 0.5, - ) - .normalized(); - - let _ = ecs - .read_resource::() - .ray(pos, pos + dir * color_range) - // TODO: Faster RNG - .until(|_| rand::random::() < 0.05) - .for_each(|_: &Block, pos| touched_blocks.push(pos)) - .cast(); - } - - let terrain = ecs.read_resource::(); - let mut block_change = ecs.write_resource::(); - for block_pos in touched_blocks { - if let Ok(block) = terrain.get(block_pos) { - let diff2 = block_pos.map(|b| b as f32).distance_squared(pos); - let fade = (1.0 - diff2 / color_range.powi(2)).max(0.0); - if let Some(mut color) = block.get_color() { - let r = color[0] as f32 + (fade * (color[0] as f32 * 0.5 - color[0] as f32)); - let g = color[1] as f32 + (fade * (color[1] as f32 * 0.3 - color[1] as f32)); - let b = color[2] as f32 + (fade * (color[2] as f32 * 0.3 - color[2] as f32)); - color[0] = r as u8; - color[1] = g as u8; - color[2] = b as u8; - block_change.set(block_pos, Block::new(block.kind(), color)); - } - } - } - - // Destroy terrain - for _ in 0..RAYS { - let dir = Vec3::new( - rand::random::() - 0.5, - rand::random::() - 0.5, - rand::random::() - 0.15, - ) - .normalized(); - - let terrain = ecs.read_resource::(); - let _ = terrain - .ray(pos, pos + dir * explosion.terrain_destruction_power) - // TODO: Faster RNG - .until(|block| block.is_liquid() || rand::random::() < 0.05) - .for_each(|block: &Block, pos| { - if block.is_explodable() { - block_change.set(pos, block.into_vacant()); + let _ = ecs + .read_resource::() + .ray(pos, pos + dir * color_range) + // TODO: Faster RNG + .until(|_| rand::random::() < 0.05) + .for_each(|_: &Block, pos| touched_blocks.push(pos)) + .cast(); } - }) - .cast(); + + let terrain = ecs.read_resource::(); + let mut block_change = ecs.write_resource::(); + for block_pos in touched_blocks { + if let Ok(block) = terrain.get(block_pos) { + let diff2 = block_pos.map(|b| b as f32).distance_squared(pos); + let fade = (1.0 - diff2 / color_range.powi(2)).max(0.0); + if let Some(mut color) = block.get_color() { + let r = color[0] as f32 + + (fade * (color[0] as f32 * 0.5 - color[0] as f32)); + let g = color[1] as f32 + + (fade * (color[1] as f32 * 0.3 - color[1] as f32)); + let b = color[2] as f32 + + (fade * (color[2] as f32 * 0.3 - color[2] as f32)); + color[0] = r as u8; + color[1] = g as u8; + color[2] = b as u8; + block_change.set(block_pos, Block::new(block.kind(), color)); + } + } + } + + // Destroy terrain + for _ in 0..RAYS { + let dir = Vec3::new( + rand::random::() - 0.5, + rand::random::() - 0.5, + rand::random::() - 0.15, + ) + .normalized(); + + let terrain = ecs.read_resource::(); + let _ = terrain + .ray(pos, pos + dir * power) + // TODO: Faster RNG + .until(|block| block.is_liquid() || rand::random::() < 0.05) + .for_each(|block: &Block, pos| { + if block.is_explodable() { + block_change.set(pos, block.into_vacant()); + } + }) + .cast(); + } + }, + } } } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 651b49a350..9a9f8cd6ba 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -58,9 +58,8 @@ impl Server { pos, explosion, owner, - friendly_damage, reagent, - } => handle_explosion(&self, pos, explosion, owner, friendly_damage, reagent), + } => handle_explosion(&self, pos, explosion, owner, reagent), ServerEvent::Shoot { entity, dir, diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index f5ab4be8f3..110895e009 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -3,7 +3,7 @@ use common::{ event::{EventBus, ServerEvent}, span, state::DeltaTime, - Explosion, + Damage, DamageSource, Damages, Explosion, RadiusEffect, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; @@ -48,16 +48,23 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Explosion { pos: pos.0, explosion: Explosion { + effects: vec![ + RadiusEffect::Damages(Damages::new( + Some(Damage { + source: DamageSource::Explosion, + value: 500.0, + }), + Some(Damage { + source: DamageSource::Explosion, + value: 500.0, + }), + )), + RadiusEffect::TerrainDestruction(4.0), + ], radius: 12.0, - max_damage: 500, - min_damage: 100, - max_heal: 0, - min_heal: 0, - terrain_destruction_power: 4.0, energy_regen: 0, }, owner: *owner, - friendly_damage: true, reagent: None, }); } @@ -71,16 +78,23 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::Explosion { pos: pos.0, explosion: Explosion { + effects: vec![ + RadiusEffect::Damages(Damages::new( + Some(Damage { + source: DamageSource::Explosion, + value: 50.0, + }), + Some(Damage { + source: DamageSource::Explosion, + value: 50.0, + }), + )), + RadiusEffect::TerrainDestruction(4.0), + ], radius: 12.0, - max_damage: 50, - min_damage: 10, - max_heal: 0, - min_heal: 0, - terrain_destruction_power: 4.0, energy_regen: 0, }, owner: *owner, - friendly_damage: true, reagent: Some(*reagent), }); } From f1f5c2b21bc62f8ce62a7882dbb58c65b9d1a2ed Mon Sep 17 00:00:00 2001 From: Sam Date: Fri, 30 Oct 2020 16:49:58 -0500 Subject: [PATCH 2/9] Added energy change server event. --- common/src/comp/energy.rs | 16 +++++++--- common/src/comp/mod.rs | 2 +- common/src/event.rs | 4 +++ common/src/states/basic_beam.rs | 12 ++++--- common/src/states/basic_melee.rs | 7 +++-- common/src/states/charged_melee.rs | 18 +++++------ common/src/states/charged_ranged.rs | 22 ++++++------- common/src/states/combo_melee.rs | 7 +++-- common/src/states/dash_melee.rs | 10 +++--- common/src/states/spin_melee.rs | 10 +++--- common/src/sys/beam.rs | 40 ++++++++++++++---------- common/src/sys/projectile.rs | 20 ++++++------ common/src/sys/stats.rs | 18 ++++++----- server/src/events/entity_manipulation.rs | 20 +++++++++--- server/src/events/mod.rs | 7 +++-- 15 files changed, 127 insertions(+), 86 deletions(-) diff --git a/common/src/comp/energy.rs b/common/src/comp/energy.rs index 55f7017f16..ff03c46a47 100644 --- a/common/src/comp/energy.rs +++ b/common/src/comp/energy.rs @@ -47,9 +47,9 @@ impl Energy { self.current = amount; } - pub fn change_by(&mut self, amount: i32, cause: EnergySource) { - self.current = ((self.current as i32 + amount).max(0) as u32).min(self.maximum); - self.last_change = Some((amount, 0.0, cause)); + pub fn change_by(&mut self, change: EnergyChange) { + self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum); + self.last_change = Some((change.amount, 0.0, change.source)); } pub fn try_change_by( @@ -62,7 +62,10 @@ impl Energy { } else if self.current as i32 + amount > self.maximum as i32 { Err(StatChangeError::Overflow) } else { - self.change_by(amount, cause); + self.change_by(EnergyChange { + amount, + source: cause, + }); Ok(()) } } @@ -73,6 +76,11 @@ impl Energy { } } +pub struct EnergyChange { + pub amount: i32, + pub source: EnergySource, +} + impl Component for Energy { type Storage = FlaggedStorage>; } diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index f2016bcb88..621ac92c23 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -43,7 +43,7 @@ pub use controller::{ Climb, ControlAction, ControlEvent, Controller, ControllerInputs, GroupManip, Input, InventoryManip, MountState, Mounting, }; -pub use energy::{Energy, EnergySource}; +pub use energy::{Energy, EnergyChange, EnergySource}; pub use group::Group; pub use inputs::CanBuild; pub use inventory::{ diff --git a/common/src/event.rs b/common/src/event.rs index 9dc0d725de..cd1d8aac12 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -109,6 +109,10 @@ pub enum ServerEvent { entity: EcsEntity, buff_change: comp::BuffChange, }, + EnergyChange { + uid: Uid, + change: comp::EnergyChange, + }, } pub struct EventBus { diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index 12ff5fd381..579777b980 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -1,5 +1,7 @@ use crate::{ - comp::{beam, humanoid, Body, CharacterState, EnergySource, Ori, Pos, StateUpdate}, + comp::{ + beam, humanoid, Body, CharacterState, EnergyChange, EnergySource, Ori, Pos, StateUpdate, + }, event::ServerEvent, states::utils::*, sync::Uid, @@ -148,10 +150,10 @@ impl CharacterBehavior for Data { }); // Consumes energy if there's enough left and ability key is held down - update.energy.change_by( - -(self.static_data.energy_drain as f32 * data.dt.0) as i32, - EnergySource::Ability, - ); + update.energy.change_by(EnergyChange { + amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32, + source: EnergySource::Ability, + }); } else { update.character = CharacterState::BasicBeam(Data { timer: Duration::default(), diff --git a/common/src/states/basic_melee.rs b/common/src/states/basic_melee.rs index ca4e002cdd..e6f0b05a11 100644 --- a/common/src/states/basic_melee.rs +++ b/common/src/states/basic_melee.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Attacking, CharacterState, EnergySource, StateUpdate}, + comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, Damage, DamageSource, Damages, Knockback, @@ -136,7 +136,10 @@ impl CharacterBehavior for Data { if let Some(attack) = data.attacking { if attack.applied && attack.hit_count > 0 { data.updater.remove::(data.entity); - update.energy.change_by(50, EnergySource::HitEnemy); + update.energy.change_by(EnergyChange { + amount: 50, + source: EnergySource::HitEnemy, + }); } } diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index deec9de03c..ab85f0fa18 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Attacking, CharacterState, EnergySource, StateUpdate}, + comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::{StageSection, *}, sys::character_behavior::*, Damage, DamageSource, Damages, Knockback, @@ -77,10 +77,10 @@ impl CharacterBehavior for Data { }); // Consumes energy if there's enough left and RMB is held down - update.energy.change_by( - -(self.static_data.energy_drain as f32 * data.dt.0) as i32, - EnergySource::Ability, - ); + update.energy.change_by(EnergyChange { + amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32, + source: EnergySource::Ability, + }); } else if data.inputs.secondary.is_pressed() && update.energy.current() >= self.static_data.energy_cost { @@ -94,10 +94,10 @@ impl CharacterBehavior for Data { }); // Consumes energy if there's enough left and RMB is held down - update.energy.change_by( - -(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32, - EnergySource::Ability, - ); + update.energy.change_by(EnergyChange { + amount: -(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32, + source: EnergySource::Ability, + }); } else { // Transitions to swing update.character = CharacterState::ChargedMelee(Data { diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index e6141cfe44..58ad57b272 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -1,8 +1,8 @@ use crate::{ comp::{ buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource}, - projectile, Body, CharacterState, EnergySource, Gravity, LightEmitter, Projectile, - StateUpdate, + projectile, Body, CharacterState, EnergyChange, EnergySource, Gravity, LightEmitter, + Projectile, StateUpdate, }, event::ServerEvent, states::utils::*, @@ -104,7 +104,7 @@ impl CharacterBehavior for Data { buff: Buff::new( BuffKind::Bleeding, BuffData { - strength: damage / 5.0, + strength: damage.value / 5.0, duration: Some(Duration::from_secs(5)), }, vec![BuffCategory::Physical], @@ -150,10 +150,10 @@ impl CharacterBehavior for Data { }); // Consumes energy if there's enough left and RMB is held down - update.energy.change_by( - -(self.static_data.energy_drain as f32 * data.dt.0) as i32, - EnergySource::Ability, - ); + update.energy.change_by(EnergyChange { + amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32, + source: EnergySource::Ability, + }); } else if data.inputs.secondary.is_pressed() { // Holds charge update.character = CharacterState::ChargedRanged(Data { @@ -165,10 +165,10 @@ impl CharacterBehavior for Data { }); // Consumes energy if there's enough left and RMB is held down - update.energy.change_by( - -(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32, - EnergySource::Ability, - ); + update.energy.change_by(EnergyChange { + amount: -(self.static_data.energy_drain as f32 * data.dt.0 / 5.0) as i32, + source: EnergySource::Ability, + }); } }, StageSection::Recover => { diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index 4fe358c1b4..d359b8d9ce 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Attacking, CharacterState, EnergySource, StateUpdate}, + comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, Damage, DamageSource, Damages, Knockback, @@ -261,7 +261,10 @@ impl CharacterBehavior for Data { next_stage: self.next_stage, }); data.updater.remove::(data.entity); - update.energy.change_by(energy, EnergySource::HitEnemy); + update.energy.change_by(EnergyChange { + amount: energy, + source: EnergySource::HitEnemy, + }); } } diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 3da10c386c..6957ee10d2 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Attacking, CharacterState, EnergySource, StateUpdate}, + comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, Damage, DamageSource, Damages, Knockback, @@ -176,10 +176,10 @@ impl CharacterBehavior for Data { } // Consumes energy if there's enough left and charge has not stopped - update.energy.change_by( - -(self.static_data.energy_drain as f32 * data.dt.0) as i32, - EnergySource::Ability, - ); + update.energy.change_by(EnergyChange { + amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32, + source: EnergySource::Ability, + }); } else { // Transitions to swing section of stage update.character = CharacterState::DashMelee(Data { diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 746e2bf58a..ffceb6fb03 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{Attacking, CharacterState, EnergySource, StateUpdate}, + comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::*, sys::{ character_behavior::{CharacterBehavior, JoinData}, @@ -154,10 +154,10 @@ impl CharacterBehavior for Data { ..*self }); // Consumes energy if there's enough left and RMB is held down - update.energy.change_by( - -(self.static_data.energy_cost as i32), - EnergySource::Ability, - ); + update.energy.change_by(EnergyChange { + amount: -(self.static_data.energy_cost as i32), + source: EnergySource::Ability, + }); } else { // Transitions to recover section of stage update.character = CharacterState::SpinMelee(Data { diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index 3cf769a9a5..143179a131 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - group, Beam, BeamSegment, Body, CharacterState, Energy, EnergySource, HealthChange, - HealthSource, Last, Loadout, Ori, Pos, Scale, Stats, + group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource, + HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, Stats, }, event::{EventBus, ServerEvent}, state::{DeltaTime, Time}, @@ -34,7 +34,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Loadout>, ReadStorage<'a, group::Group>, ReadStorage<'a, CharacterState>, - WriteStorage<'a, Energy>, + ReadStorage<'a, Energy>, WriteStorage<'a, BeamSegment>, WriteStorage<'a, Beam>, ); @@ -57,7 +57,7 @@ impl<'a> System<'a> for Sys { loadouts, groups, character_states, - mut energies, + energies, mut beam_segments, mut beams, ): Self::SystemData, @@ -197,20 +197,26 @@ impl<'a> System<'a> for Sys { }, }); } - if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) { - energy_mut.change_by( - beam_segment.energy_regen as i32, - EnergySource::HitEnemy, - ); + if let Some(uid) = beam_segment.owner { + server_emitter.emit(ServerEvent::EnergyChange { + uid, + change: EnergyChange { + amount: beam_segment.energy_regen as i32, + source: EnergySource::HitEnemy, + }, + }); } - } else if let Some(energy_mut) = beam_owner.and_then(|o| energies.get_mut(o)) { - if energy_mut - .try_change_by( - -(beam_segment.energy_cost as i32), // Stamina use - EnergySource::Ability, - ) - .is_ok() - { + } else if let Some(energy) = beam_owner.and_then(|o| energies.get(o)) { + if energy.current() > beam_segment.energy_cost { + if let Some(uid) = beam_segment.owner { + server_emitter.emit(ServerEvent::EnergyChange { + uid, + change: EnergyChange { + amount: -(beam_segment.energy_cost as i32), // Stamina use + source: EnergySource::Ability, + }, + }) + } server_emitter.emit(ServerEvent::Damage { uid: *uid_b, change, diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index a8dce0663e..5959a6ce2f 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -1,8 +1,8 @@ use crate::{ comp::{ buff::{BuffChange, BuffSource}, - projectile, Energy, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState, Pos, - Projectile, Vel, + projectile, EnergyChange, EnergySource, Group, HealthSource, Loadout, Ori, PhysicsState, + Pos, Projectile, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, @@ -32,7 +32,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Vel>, WriteStorage<'a, Ori>, WriteStorage<'a, Projectile>, - WriteStorage<'a, Energy>, ReadStorage<'a, Loadout>, ReadStorage<'a, Group>, ); @@ -51,7 +50,6 @@ impl<'a> System<'a> for Sys { velocities, mut orientations, mut projectiles, - mut energies, loadouts, groups, ): Self::SystemData, @@ -129,12 +127,14 @@ impl<'a> System<'a> for Sys { } }, projectile::Effect::RewardEnergy(energy) => { - if let Some(energy_mut) = projectile - .owner - .and_then(|o| uid_allocator.retrieve_entity_internal(o.into())) - .and_then(|o| energies.get_mut(o)) - { - energy_mut.change_by(energy as i32, EnergySource::HitEnemy); + if let Some(uid) = projectile.owner { + server_emitter.emit(ServerEvent::EnergyChange { + uid, + change: EnergyChange { + amount: energy as i32, + source: EnergySource::HitEnemy, + }, + }); } }, projectile::Effect::Explode(e) => { diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index 723904aa4a..bf05054cb6 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{CharacterState, Energy, EnergySource, HealthSource, Stats}, + comp::{CharacterState, Energy, EnergyChange, EnergySource, HealthSource, Stats}, event::{EventBus, ServerEvent}, metrics::SysMetrics, span, @@ -96,11 +96,12 @@ impl<'a> System<'a> for Sys { if res { let mut energy = energy.get_mut_unchecked(); // Have to account for Calc I differential equations due to acceleration - energy.change_by( - (energy.regen_rate * dt.0 + ENERGY_REGEN_ACCEL * dt.0.powf(2.0) / 2.0) + energy.change_by(EnergyChange { + amount: (energy.regen_rate * dt.0 + + ENERGY_REGEN_ACCEL * dt.0.powf(2.0) / 2.0) as i32, - EnergySource::Regen, - ); + source: EnergySource::Regen, + }); energy.regen_rate = (energy.regen_rate + ENERGY_REGEN_ACCEL * dt.0).min(100.0); } @@ -130,9 +131,10 @@ impl<'a> System<'a> for Sys { }; if res { - energy - .get_mut_unchecked() - .change_by(-3, EnergySource::Regen); + energy.get_mut_unchecked().change_by(EnergyChange { + amount: -3, + source: EnergySource::Regen, + }); } }, // Non-combat abilities that consume energy; diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 5c6c8d0ab9..2f0e36269e 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -8,7 +8,8 @@ use common::{ comp::{ self, buff, chat::{KillSource, KillType}, - object, Alignment, Body, Group, HealthChange, HealthSource, Item, Player, Pos, Stats, + object, Alignment, Body, Energy, EnergyChange, Group, HealthChange, HealthSource, Item, + Player, Pos, Stats, }, lottery::Lottery, msg::{PlayerListUpdate, ServerGeneral}, @@ -596,10 +597,10 @@ pub fn handle_explosion( if let Some(energy) = ecs.write_storage::().get_mut(owner) { - energy.change_by( - explosion.energy_regen as i32, - comp::EnergySource::HitEnemy, - ); + energy.change_by(EnergyChange { + amount: explosion.energy_regen as i32, + source: comp::EnergySource::HitEnemy, + }); } } } @@ -748,3 +749,12 @@ pub fn handle_buff(server: &mut Server, entity: EcsEntity, buff_change: buff::Bu } } } + +pub fn handle_energy_change(server: &Server, uid: Uid, change: EnergyChange) { + let ecs = &server.state.ecs(); + if let Some(entity) = ecs.entity_from_uid(uid.into()) { + if let Some(energy) = ecs.write_storage::().get_mut(entity) { + energy.change_by(change); + } + } +} diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 9a9f8cd6ba..358363c4a5 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -8,8 +8,8 @@ use entity_creation::{ handle_loaded_character_data, handle_shockwave, handle_shoot, }; use entity_manipulation::{ - handle_buff, handle_damage, handle_destroy, handle_explosion, handle_knockback, - handle_land_on_ground, handle_level_up, handle_respawn, + handle_buff, handle_damage, handle_destroy, handle_energy_change, handle_explosion, + handle_knockback, handle_land_on_ground, handle_level_up, handle_respawn, }; use group_manip::handle_group; use interaction::{handle_lantern, handle_mount, handle_possess, handle_unmount}; @@ -136,6 +136,9 @@ impl Server { entity, buff_change, } => handle_buff(self, entity, buff_change), + ServerEvent::EnergyChange { uid, change } => { + handle_energy_change(&self, uid, change) + }, } } From b8f722af8dd5dacbc28ebf5509ce47956beb5256 Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 31 Oct 2020 13:44:00 -0500 Subject: [PATCH 3/9] Removed most hardcoded ability keys. --- common/src/comp/ability.rs | 5 +++++ common/src/states/charged_melee.rs | 6 ++++-- common/src/states/charged_ranged.rs | 8 +++++--- common/src/states/combo_melee.rs | 8 ++++++-- common/src/states/dash_melee.rs | 10 +++++++--- common/src/states/spin_melee.rs | 9 +++++++-- 6 files changed, 34 insertions(+), 12 deletions(-) diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 85b819fe95..af635ab264 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -452,6 +452,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { swing_duration: *swing_duration, recover_duration: *recover_duration, is_interruptible: *is_interruptible, + ability_key: key, }, auto_charge: false, timer: Duration::default(), @@ -489,6 +490,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { speed_increase: 1.0 - *speed_increase, max_speed_increase: *max_speed_increase - 1.0, is_interruptible: *is_interruptible, + ability_key: key, }, stage: 1, combo: 0, @@ -552,6 +554,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { is_interruptible: *is_interruptible, forward_speed: *forward_speed, num_spins: *num_spins, + ability_key: key, }, timer: Duration::default(), spins_remaining: *num_spins - 1, @@ -583,6 +586,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { charge_duration: *charge_duration, swing_duration: *swing_duration, recover_duration: *recover_duration, + ability_key: key, }, stage_section: StageSection::Charge, timer: Duration::default(), @@ -619,6 +623,7 @@ impl From<(&CharacterAbility, AbilityKey)> for CharacterState { projectile_gravity: *projectile_gravity, initial_projectile_speed: *initial_projectile_speed, max_projectile_speed: *max_projectile_speed, + ability_key: key, }, timer: Duration::default(), stage_section: StageSection::Buildup, diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index ab85f0fa18..9c6216bc84 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -32,6 +32,8 @@ pub struct StaticData { pub swing_duration: Duration, /// How long the state has until exiting pub recover_duration: Duration, + /// What key is used to press ability + pub ability_key: AbilityKey, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -58,7 +60,7 @@ impl CharacterBehavior for Data { match self.stage_section { StageSection::Charge => { - if data.inputs.secondary.is_pressed() + if ability_key_is_pressed(data, self.static_data.ability_key) && update.energy.current() >= self.static_data.energy_cost && self.timer < self.static_data.charge_duration { @@ -81,7 +83,7 @@ impl CharacterBehavior for Data { amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32, source: EnergySource::Ability, }); - } else if data.inputs.secondary.is_pressed() + } else if ability_key_is_pressed(data, self.static_data.ability_key) && update.energy.current() >= self.static_data.energy_cost { // Maintains charge diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index 58ad57b272..bbd29dee68 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -37,6 +37,8 @@ pub struct StaticData { pub projectile_gravity: Option, pub initial_projectile_speed: f32, pub max_projectile_speed: f32, + /// What key is used to press ability + pub ability_key: AbilityKey, } #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -80,7 +82,7 @@ impl CharacterBehavior for Data { } }, StageSection::Charge => { - if !data.inputs.secondary.is_pressed() && !self.exhausted { + if !ability_key_is_pressed(data, self.static_data.ability_key) && !self.exhausted { let charge_frac = (self.timer.as_secs_f32() / self.static_data.charge_duration.as_secs_f32()) .min(1.0); @@ -138,7 +140,7 @@ impl CharacterBehavior for Data { ..*self }); } else if self.timer < self.static_data.charge_duration - && data.inputs.secondary.is_pressed() + && ability_key_is_pressed(data, self.static_data.ability_key) { // Charges update.character = CharacterState::ChargedRanged(Data { @@ -154,7 +156,7 @@ impl CharacterBehavior for Data { amount: -(self.static_data.energy_drain as f32 * data.dt.0) as i32, source: EnergySource::Ability, }); - } else if data.inputs.secondary.is_pressed() { + } else if ability_key_is_pressed(data, self.static_data.ability_key) { // Holds charge update.character = CharacterState::ChargedRanged(Data { timer: self diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index d359b8d9ce..df2c6f885d 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -54,6 +54,8 @@ pub struct StaticData { pub max_speed_increase: f32, /// Whether the state can be interrupted by other abilities pub is_interruptible: bool, + /// What key is used to press ability + pub ability_key: AbilityKey, } /// A sequence of attacks that can incrementally become faster and more /// damaging. @@ -84,7 +86,9 @@ impl CharacterBehavior for Data { let stage_index = (self.stage - 1) as usize; // Allows for other states to interrupt this state - if self.static_data.is_interruptible && !data.inputs.primary.is_pressed() { + if self.static_data.is_interruptible + && !ability_key_is_pressed(data, self.static_data.ability_key) + { handle_interrupt(data, &mut update); match update.character { CharacterState::ComboMelee(_) => {}, @@ -183,7 +187,7 @@ impl CharacterBehavior for Data { StageSection::Recover => { if self.timer < self.static_data.stage_data[stage_index].base_recover_duration { // Recovers - if data.inputs.primary.is_pressed() { + if ability_key_is_pressed(data, self.static_data.ability_key) { // Checks if state will transition to next stage after recover update.character = CharacterState::ComboMelee(Data { static_data: self.static_data.clone(), diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 6957ee10d2..8e141c2e68 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -39,6 +39,8 @@ pub struct StaticData { pub recover_duration: Duration, /// Whether the state can be interrupted by other abilities pub is_interruptible: bool, + /// What key is used to press ability + pub ability_key: AbilityKey, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -66,7 +68,9 @@ impl CharacterBehavior for Data { handle_move(data, &mut update, 0.1); // Allows for other states to interrupt this state - if self.static_data.is_interruptible && !data.inputs.secondary.is_pressed() { + if self.static_data.is_interruptible + && !ability_key_is_pressed(data, self.static_data.ability_key) + { handle_interrupt(data, &mut update); match update.character { CharacterState::DashMelee(_) => {}, @@ -90,7 +94,7 @@ impl CharacterBehavior for Data { } else { // Transitions to charge section of stage update.character = CharacterState::DashMelee(Data { - auto_charge: !data.inputs.secondary.is_pressed(), + auto_charge: !ability_key_is_pressed(data, self.static_data.ability_key), timer: Duration::default(), stage_section: StageSection::Charge, ..*self @@ -100,7 +104,7 @@ impl CharacterBehavior for Data { StageSection::Charge => { if (self.static_data.infinite_charge || self.timer < self.static_data.charge_duration) - && (data.inputs.secondary.is_pressed() + && (ability_key_is_pressed(data, self.static_data.ability_key) || (self.auto_charge && self.timer < self.static_data.charge_duration)) && update.energy.current() > 0 { diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index ffceb6fb03..452bded7b0 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -38,6 +38,8 @@ pub struct StaticData { pub forward_speed: f32, /// Number of spins pub num_spins: u32, + /// What key is used to press ability + pub ability_key: AbilityKey, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -65,7 +67,9 @@ impl CharacterBehavior for Data { } // Allows for other states to interrupt this state - if self.static_data.is_interruptible && !data.inputs.ability3.is_pressed() { + if self.static_data.is_interruptible + && !ability_key_is_pressed(data, self.static_data.ability_key) + { handle_interrupt(data, &mut update); match update.character { CharacterState::SpinMelee(_) => {}, @@ -140,7 +144,8 @@ impl CharacterBehavior for Data { }); } else if update.energy.current() >= self.static_data.energy_cost && (self.spins_remaining != 0 - || (self.static_data.is_infinite && data.inputs.secondary.is_pressed())) + || (self.static_data.is_infinite + && ability_key_is_pressed(data, self.static_data.ability_key))) { let new_spins_remaining = if self.static_data.is_infinite { self.spins_remaining From c48c022f7e970a2b753ede1763223d47ab5d5acb Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 31 Oct 2020 17:34:08 -0500 Subject: [PATCH 4/9] Separated out health from stats component. --- client/src/lib.rs | 10 +- common/src/comp/health.rs | 129 +++ common/src/comp/mod.rs | 4 +- common/src/comp/stats.rs | 135 +--- common/src/event.rs | 1 + common/src/msg/ecs_packet.rs | 5 + common/src/state.rs | 1 + common/src/sys/agent.rs | 43 +- common/src/sys/beam.rs | 14 +- common/src/sys/buff.rs | 31 +- common/src/sys/character_behavior.rs | 14 +- common/src/sys/melee.rs | 12 +- common/src/sys/shockwave.rs | 14 +- common/src/sys/stats.rs | 45 +- server/src/cmd.rs | 40 +- server/src/events/entity_creation.rs | 7 +- server/src/events/entity_manipulation.rs | 27 +- server/src/events/inventory_manip.rs | 8 +- server/src/events/mod.rs | 3 +- .../src/persistence/character/conversions.rs | 4 +- server/src/state_ext.rs | 7 +- server/src/sys/message.rs | 743 ++++++++++++++++++ server/src/sys/msg/in_game.rs | 10 +- server/src/sys/sentinel.rs | 17 +- server/src/sys/terrain.rs | 7 +- .../audio/sfx/event_mapper/combat/tests.rs | 2 + voxygen/src/ecs/sys/floater.rs | 33 +- voxygen/src/hud/group.rs | 137 ++-- voxygen/src/hud/mod.rs | 54 +- voxygen/src/hud/overhead.rs | 19 +- voxygen/src/hud/skillbar.rs | 22 +- voxygen/src/scene/figure/mod.rs | 29 +- voxygen/src/scene/mod.rs | 4 +- 33 files changed, 1228 insertions(+), 403 deletions(-) create mode 100644 common/src/comp/health.rs create mode 100644 server/src/sys/message.rs diff --git a/client/src/lib.rs b/client/src/lib.rs index 46fefef2d2..8aa29e35f3 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -583,16 +583,16 @@ impl Client { } pub fn pick_up(&mut self, entity: EcsEntity) { - // Get the stats component from the entity + // Get the health component from the entity if let Some(uid) = self.state.read_component_copied(entity) { // If we're dead, exit before sending the message if self .state .ecs() - .read_storage::() + .read_storage::() .get(self.entity) - .map_or(false, |s| s.is_dead) + .map_or(false, |h| h.is_dead) { return; } @@ -731,9 +731,9 @@ impl Client { if self .state .ecs() - .read_storage::() + .read_storage::() .get(self.entity) - .map_or(false, |s| s.is_dead) + .map_or(false, |h| h.is_dead) { self.send_msg(ClientGeneral::ControlEvent(ControlEvent::Respawn)); } diff --git a/common/src/comp/health.rs b/common/src/comp/health.rs new file mode 100644 index 0000000000..7dd3ff3385 --- /dev/null +++ b/common/src/comp/health.rs @@ -0,0 +1,129 @@ +use crate::{comp::Body, sync::Uid}; +use serde::{Deserialize, Serialize}; +use specs::{Component, FlaggedStorage}; +use specs_idvs::IdvStorage; + +/// Specifies what and how much changed current health +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub struct HealthChange { + pub amount: i32, + pub cause: HealthSource, +} + +#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] +pub enum HealthSource { + Attack { by: Uid }, // TODO: Implement weapon + Projectile { owner: Option }, + Explosion { owner: Option }, + Energy { owner: Option }, + Buff { owner: Option }, + Suicide, + World, + Revive, + Command, + LevelUp, + Item, + Healing { by: Option }, + Unknown, +} + +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct Health { + base_max: u32, + current: u32, + maximum: u32, + pub last_change: (f64, HealthChange), + pub is_dead: bool, +} + +impl Health { + pub fn new(body: Body, level: u32) -> Self { + let mut health = Health::empty(); + + health.update_max_hp(Some(body), level); + health.set_to(health.maximum(), HealthSource::Revive); + + health + } + + pub fn empty() -> Self { + Health { + current: 0, + maximum: 0, + base_max: 0, + last_change: (0.0, HealthChange { + amount: 0, + cause: HealthSource::Revive, + }), + is_dead: false, + } + } + + pub fn current(&self) -> u32 { self.current } + + pub fn maximum(&self) -> u32 { self.maximum } + + pub fn set_to(&mut self, amount: u32, cause: HealthSource) { + let amount = amount.min(self.maximum); + self.last_change = (0.0, HealthChange { + amount: amount as i32 - self.current as i32, + cause, + }); + self.current = amount; + } + + pub fn change_by(&mut self, change: HealthChange) { + self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum); + self.last_change = (0.0, change); + } + + // This function changes the modified max health value, not the base health + // value. The modified health value takes into account buffs and other temporary + // changes to max health. + pub fn set_maximum(&mut self, amount: u32) { + self.maximum = amount; + self.current = self.current.min(self.maximum); + } + + // This is private because max hp is based on the level + fn set_base_max(&mut self, amount: u32) { + self.base_max = amount; + self.current = self.current.min(self.maximum); + } + + pub fn reset_max(&mut self) { self.maximum = self.base_max; } + + pub fn should_die(&self) -> bool { self.current == 0 } + + pub fn revive(&mut self) { + self.set_to(self.maximum(), HealthSource::Revive); + self.is_dead = false; + } + + // TODO: Delete this once stat points will be a thing + pub fn update_max_hp(&mut self, body: Option, level: u32) { + if let Some(body) = body { + self.set_base_max(body.base_health() + body.base_health_increase() * level); + self.set_maximum(body.base_health() + body.base_health_increase() * level); + } + } + + pub fn with_max_health(mut self, amount: u32) -> Self { + self.maximum = amount; + self.current = amount; + self + } +} + +impl Component for Health { + type Storage = FlaggedStorage>; +} + +#[derive(Copy, Clone, Debug, Serialize, Deserialize)] +pub struct Dying { + pub cause: HealthSource, +} + +impl Component for Dying { + type Storage = IdvStorage; +} diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index 621ac92c23..d86401d67e 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -9,6 +9,7 @@ pub mod chat; mod controller; mod energy; pub mod group; +mod health; mod inputs; mod inventory; mod last; @@ -45,6 +46,7 @@ pub use controller::{ }; pub use energy::{Energy, EnergyChange, EnergySource}; pub use group::Group; +pub use health::{Health, HealthChange, HealthSource}; pub use inputs::CanBuild; pub use inventory::{ item, @@ -59,5 +61,5 @@ pub use player::Player; pub use projectile::Projectile; pub use shockwave::{Shockwave, ShockwaveHitEntities}; pub use skills::{Skill, SkillGroup, SkillGroupType, SkillSet}; -pub use stats::{Exp, HealthChange, HealthSource, Level, Stats}; +pub use stats::{Exp, Level, Stats}; pub use visual::{LightAnimation, LightEmitter}; diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 7289bfbdf7..11b063689a 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -1,45 +1,12 @@ use crate::{ comp, comp::{body::humanoid::Species, skills::SkillSet, Body}, - sync::Uid, }; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; use std::{error::Error, fmt}; -/// Specifies what and how much changed current health -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub struct HealthChange { - pub amount: i32, - pub cause: HealthSource, -} - -#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] -pub enum HealthSource { - Attack { by: Uid }, // TODO: Implement weapon - Projectile { owner: Option }, - Explosion { owner: Option }, - Energy { owner: Option }, - Buff { owner: Option }, - Suicide, - World, - Revive, - Command, - LevelUp, - Item, - Healing { by: Option }, - Unknown, -} - -#[derive(Clone, Copy, Debug, Serialize, Deserialize)] -pub struct Health { - base_max: u32, - current: u32, - maximum: u32, - pub last_change: (f64, HealthChange), -} - #[derive(Clone, Copy, Debug, Serialize, Deserialize)] pub struct Exp { current: u32, @@ -51,41 +18,6 @@ pub struct Level { amount: u32, } -impl Health { - pub fn current(&self) -> u32 { self.current } - - pub fn maximum(&self) -> u32 { self.maximum } - - pub fn set_to(&mut self, amount: u32, cause: HealthSource) { - let amount = amount.min(self.maximum); - self.last_change = (0.0, HealthChange { - amount: amount as i32 - self.current as i32, - cause, - }); - self.current = amount; - } - - pub fn change_by(&mut self, change: HealthChange) { - self.current = ((self.current as i32 + change.amount).max(0) as u32).min(self.maximum); - self.last_change = (0.0, change); - } - - // This function changes the modified max health value, not the base health - // value. The modified health value takes into account buffs and other temporary - // changes to max health. - pub fn set_maximum(&mut self, amount: u32) { - self.maximum = amount; - self.current = self.current.min(self.maximum); - } - - // This is private because max hp is based on the level - fn set_base_max(&mut self, amount: u32) { - self.base_max = amount; - self.current = self.current.min(self.maximum); - } - - pub fn reset_max(&mut self) { self.maximum = self.base_max; } -} #[derive(Debug)] pub enum StatChangeError { Underflow, @@ -139,35 +71,15 @@ impl Level { #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Stats { pub name: String, - pub health: Health, pub level: Level, pub exp: Exp, pub skill_set: SkillSet, pub endurance: u32, pub fitness: u32, pub willpower: u32, - pub is_dead: bool, pub body_type: Body, } -impl Stats { - pub fn should_die(&self) -> bool { self.health.current == 0 } - - pub fn revive(&mut self) { - self.health - .set_to(self.health.maximum(), HealthSource::Revive); - self.is_dead = false; - } - - // TODO: Delete this once stat points will be a thing - pub fn update_max_hp(&mut self, body: Body) { - self.health - .set_base_max(body.base_health() + body.base_health_increase() * self.level.amount); - self.health - .set_maximum(body.base_health() + body.base_health_increase() * self.level.amount); - } -} - impl Stats { pub fn new(name: String, body: Body) -> Self { let species = if let comp::Body::Humanoid(hbody) = body { @@ -189,17 +101,8 @@ impl Stats { None => (0, 0, 0), }; - let mut stats = Self { + Self { name, - health: Health { - current: 0, - maximum: 0, - base_max: 0, - last_change: (0.0, HealthChange { - amount: 0, - cause: HealthSource::Revive, - }), - }, level: Level { amount: 1 }, exp: Exp { current: 0, @@ -209,17 +112,8 @@ impl Stats { endurance, fitness, willpower, - is_dead: false, body_type: body, - }; - - stats.update_max_hp(body); - - stats - .health - .set_to(stats.health.maximum(), HealthSource::Revive); - - stats + } } /// Creates an empty `Stats` instance - used during character loading from @@ -227,15 +121,6 @@ impl Stats { pub fn empty() -> Self { Self { name: "".to_owned(), - health: Health { - current: 0, - maximum: 0, - base_max: 0, - last_change: (0.0, HealthChange { - amount: 0, - cause: HealthSource::Revive, - }), - }, level: Level { amount: 1 }, exp: Exp { current: 0, @@ -245,27 +130,11 @@ impl Stats { endurance: 0, fitness: 0, willpower: 0, - is_dead: false, body_type: comp::Body::Humanoid(comp::body::humanoid::Body::random()), } } - - pub fn with_max_health(mut self, amount: u32) -> Self { - self.health.maximum = amount; - self.health.current = amount; - self - } } impl Component for Stats { type Storage = FlaggedStorage>; } - -#[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct Dying { - pub cause: HealthSource, -} - -impl Component for Dying { - type Storage = IdvStorage; -} diff --git a/common/src/event.rs b/common/src/event.rs index cd1d8aac12..41965191da 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -92,6 +92,7 @@ pub enum ServerEvent { CreateNpc { pos: comp::Pos, stats: comp::Stats, + health: comp::Health, loadout: comp::Loadout, body: comp::Body, agent: Option, diff --git a/common/src/msg/ecs_packet.rs b/common/src/msg/ecs_packet.rs index 41ab1bda71..4aff0fbdd5 100644 --- a/common/src/msg/ecs_packet.rs +++ b/common/src/msg/ecs_packet.rs @@ -15,6 +15,7 @@ sum_type! { Stats(comp::Stats), Buffs(comp::Buffs), Energy(comp::Energy), + Health(comp::Health), LightEmitter(comp::LightEmitter), Item(comp::Item), Scale(comp::Scale), @@ -45,6 +46,7 @@ sum_type! { Stats(PhantomData), Buffs(PhantomData), Energy(PhantomData), + Health(PhantomData), LightEmitter(PhantomData), Item(PhantomData), Scale(PhantomData), @@ -75,6 +77,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Stats(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Buffs(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_insert(comp, entity, world), + EcsCompPacket::Health(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::LightEmitter(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_insert(comp, entity, world), EcsCompPacket::Scale(comp) => sync::handle_insert(comp, entity, world), @@ -103,6 +106,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPacket::Stats(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Buffs(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Energy(comp) => sync::handle_modify(comp, entity, world), + EcsCompPacket::Health(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::LightEmitter(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Item(comp) => sync::handle_modify(comp, entity, world), EcsCompPacket::Scale(comp) => sync::handle_modify(comp, entity, world), @@ -131,6 +135,7 @@ impl sync::CompPacket for EcsCompPacket { EcsCompPhantom::Stats(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Buffs(_) => sync::handle_remove::(entity, world), EcsCompPhantom::Energy(_) => sync::handle_remove::(entity, world), + EcsCompPhantom::Health(_) => sync::handle_remove::(entity, world), EcsCompPhantom::LightEmitter(_) => { sync::handle_remove::(entity, world) }, diff --git a/common/src/state.rs b/common/src/state.rs index cbf00daaf2..c3ec24b5eb 100644 --- a/common/src/state.rs +++ b/common/src/state.rs @@ -114,6 +114,7 @@ impl State { ecs.register::(); ecs.register::(); ecs.register::(); + ecs.register::(); ecs.register::(); ecs.register::(); ecs.register::(); diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index fa9af1771e..27e4a8c600 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -6,7 +6,7 @@ use crate::{ group::Invite, item::{tool::ToolKind, ItemKind}, Agent, Alignment, Body, CharacterState, ControlAction, ControlEvent, Controller, Energy, - GroupManip, LightEmitter, Loadout, MountState, Ori, PhysicsState, Pos, Scale, Stats, + GroupManip, Health, LightEmitter, Loadout, MountState, Ori, PhysicsState, Pos, Scale, UnresolvedChatMsg, Vel, }, event::{EventBus, ServerEvent}, @@ -46,7 +46,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Vel>, ReadStorage<'a, Ori>, ReadStorage<'a, Scale>, - ReadStorage<'a, Stats>, + ReadStorage<'a, Health>, ReadStorage<'a, Loadout>, ReadStorage<'a, PhysicsState>, ReadStorage<'a, Uid>, @@ -76,7 +76,7 @@ impl<'a> System<'a> for Sys { velocities, orientations, scales, - stats, + healths, loadouts, physics_states, uids, @@ -261,8 +261,8 @@ impl<'a> System<'a> for Sys { } }, Activity::Follow { target, chaser } => { - if let (Some(tgt_pos), _tgt_stats) = - (positions.get(*target), stats.get(*target)) + if let (Some(tgt_pos), _tgt_health) = + (positions.get(*target), healths.get(*target)) { let dist = pos.0.distance(tgt_pos.0); // Follow, or return to idle @@ -329,9 +329,9 @@ impl<'a> System<'a> for Sys { _ => Tactic::Melee, }; - if let (Some(tgt_pos), Some(tgt_stats), tgt_alignment) = ( + if let (Some(tgt_pos), Some(tgt_health), tgt_alignment) = ( positions.get(*target), - stats.get(*target), + healths.get(*target), alignments.get(*target).copied().unwrap_or( uids.get(*target) .copied() @@ -346,7 +346,7 @@ impl<'a> System<'a> for Sys { // Don't attack entities we are passive towards // TODO: This is here, it's a bit of a hack if let Some(alignment) = alignment { - if alignment.passive_towards(tgt_alignment) || tgt_stats.is_dead { + if alignment.passive_towards(tgt_alignment) || tgt_health.is_dead { do_idle = true; break 'activity; } @@ -354,9 +354,9 @@ impl<'a> System<'a> for Sys { let dist_sqrd = pos.0.distance_squared(tgt_pos.0); - let damage = stats + let damage = healths .get(entity) - .map(|s| s.health.current() as f32 / s.health.maximum() as f32) + .map(|h| h.current() as f32 / h.maximum() as f32) .unwrap_or(0.5); // Flee @@ -557,9 +557,9 @@ impl<'a> System<'a> for Sys { if choose_target { // Search for new targets (this looks expensive, but it's only run occasionally) // TODO: Replace this with a better system that doesn't consider *all* entities - let closest_entity = (&entities, &positions, &stats, alignments.maybe(), char_states.maybe()) + let closest_entity = (&entities, &positions, &healths, alignments.maybe(), char_states.maybe()) .join() - .filter(|(e, e_pos, e_stats, e_alignment, char_state)| { + .filter(|(e, e_pos, e_health, e_alignment, char_state)| { let mut search_dist = SEARCH_DIST; let mut listen_dist = LISTEN_DIST; if char_state.map_or(false, |c_s| c_s.is_stealthy()) { @@ -573,7 +573,7 @@ impl<'a> System<'a> for Sys { // Within listen distance || e_pos.0.distance_squared(pos.0) < listen_dist.powf(2.0)) && *e != entity - && !e_stats.is_dead + && !e_health.is_dead && alignment .and_then(|a| e_alignment.map(|b| a.hostile_towards(*b))) .unwrap_or(false) @@ -602,20 +602,20 @@ impl<'a> System<'a> for Sys { // last!) --- // Attack a target that's attacking us - if let Some(my_stats) = stats.get(entity) { + if let Some(my_health) = healths.get(entity) { // Only if the attack was recent - if my_stats.health.last_change.0 < 3.0 { + if my_health.last_change.0 < 3.0 { if let comp::HealthSource::Attack { by } | comp::HealthSource::Projectile { owner: Some(by) } | comp::HealthSource::Energy { owner: Some(by) } | comp::HealthSource::Buff { owner: Some(by) } | comp::HealthSource::Explosion { owner: Some(by) } = - my_stats.health.last_change.1.cause + my_health.last_change.1.cause { if !agent.activity.is_attack() { if let Some(attacker) = uid_allocator.retrieve_entity_internal(by.id()) { - if stats.get(attacker).map_or(false, |a| !a.is_dead) { + if healths.get(attacker).map_or(false, |a| !a.is_dead) { match agent.activity { Activity::Attack { target, .. } if target == attacker => {}, _ => { @@ -658,12 +658,9 @@ impl<'a> System<'a> for Sys { } // Attack owner's attacker - let owner_stats = stats.get(owner)?; - if owner_stats.health.last_change.0 < 5.0 - && owner_stats.health.last_change.1.amount < 0 - { - if let comp::HealthSource::Attack { by } = - owner_stats.health.last_change.1.cause + let owner_health = healths.get(owner)?; + if owner_health.last_change.0 < 5.0 && owner_health.last_change.1.amount < 0 { + if let comp::HealthSource::Attack { by } = owner_health.last_change.1.cause { if !agent.activity.is_attack() { let attacker = uid_allocator.retrieve_entity_internal(by.id())?; diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index 143179a131..2f81ce7bc3 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource, - HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, Stats, + group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource, Health, + HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, }, event::{EventBus, ServerEvent}, state::{DeltaTime, Time}, @@ -30,7 +30,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Ori>, ReadStorage<'a, Scale>, ReadStorage<'a, Body>, - ReadStorage<'a, Stats>, + ReadStorage<'a, Health>, ReadStorage<'a, Loadout>, ReadStorage<'a, group::Group>, ReadStorage<'a, CharacterState>, @@ -53,7 +53,7 @@ impl<'a> System<'a> for Sys { orientations, scales, bodies, - stats, + healths, loadouts, groups, character_states, @@ -124,7 +124,7 @@ impl<'a> System<'a> for Sys { ori_b, scale_b_maybe, character_b, - stats_b, + health_b, body_b, ) in ( &entities, @@ -135,7 +135,7 @@ impl<'a> System<'a> for Sys { &orientations, scales.maybe(), character_states.maybe(), - &stats, + &healths, &bodies, ) .join() @@ -152,7 +152,7 @@ impl<'a> System<'a> for Sys { // Check if it is a hit let hit = entity != b - && !stats_b.is_dead + && !health_b.is_dead // Collision shapes && (sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, pos_b.0, rad_b, height_b) || last_pos_b_maybe.map_or(false, |pos_maybe| {sphere_wedge_cylinder_collision(pos.0, frame_start_dist, frame_end_dist, *ori.0, beam_segment.angle, (pos_maybe.0).0, rad_b, height_b)})); diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 3cff89f8fc..271f19d49a 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, HealthChange, - HealthSource, Loadout, ModifierKind, Stats, + BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, Health, HealthChange, + HealthSource, Loadout, ModifierKind, }, event::{EventBus, ServerEvent}, state::DeltaTime, @@ -19,19 +19,20 @@ impl<'a> System<'a> for Sys { Read<'a, EventBus>, ReadStorage<'a, Uid>, ReadStorage<'a, Loadout>, - WriteStorage<'a, Stats>, + WriteStorage<'a, Health>, WriteStorage<'a, Buffs>, ); fn run( &mut self, - (entities, dt, server_bus, uids, loadouts, mut stats, mut buffs): Self::SystemData, + (entities, dt, server_bus, uids, loadouts, mut healths, mut buffs): Self::SystemData, ) { let mut server_emitter = server_bus.emitter(); // Set to false to avoid spamming server buffs.set_event_emission(false); - stats.set_event_emission(false); - for (entity, buff_comp, uid, stat) in (&entities, &mut buffs, &uids, &mut stats).join() { + healths.set_event_emission(false); + for (entity, buff_comp, uid, health) in (&entities, &mut buffs, &uids, &mut healths).join() + { let mut expired_buffs = Vec::::new(); for (id, buff) in buff_comp.buffs.iter_mut() { // Tick the buff and subtract delta from it @@ -63,8 +64,8 @@ impl<'a> System<'a> for Sys { } } - // Call to reset stats to base values - stat.health.reset_max(); + // Call to reset health to base values + health.reset_max(); // Iterator over the lists of buffs by kind for buff_ids in buff_comp.kinds.values() { @@ -104,14 +105,10 @@ impl<'a> System<'a> for Sys { }, BuffEffect::MaxHealthModifier { value, kind } => match kind { ModifierKind::Multiplicative => { - stat.health.set_maximum( - (stat.health.maximum() as f32 * *value) as u32, - ); + health.set_maximum((health.maximum() as f32 * *value) as u32); }, ModifierKind::Additive => { - stat.health.set_maximum( - (stat.health.maximum() as f32 + *value) as u32, - ); + health.set_maximum((health.maximum() as f32 + *value) as u32); }, }, }; @@ -127,8 +124,8 @@ impl<'a> System<'a> for Sys { }); } - // Remove stats that don't persist on death - if stat.is_dead { + // Remove buffs that don't persist on death + if health.is_dead { server_emitter.emit(ServerEvent::Buff { entity, buff_change: BuffChange::RemoveByCategory { @@ -141,6 +138,6 @@ impl<'a> System<'a> for Sys { } // Turned back to true buffs.set_event_emission(true); - stats.set_event_emission(true); + healths.set_event_emission(true); } } diff --git a/common/src/sys/character_behavior.rs b/common/src/sys/character_behavior.rs index 8d06b8a89a..c0bb15cd57 100644 --- a/common/src/sys/character_behavior.rs +++ b/common/src/sys/character_behavior.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ Attacking, Beam, Body, CharacterState, ControlAction, Controller, ControllerInputs, Energy, - Loadout, Mounting, Ori, PhysicsState, Pos, StateUpdate, Stats, Vel, + Health, Loadout, Mounting, Ori, PhysicsState, Pos, StateUpdate, Vel, }, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, @@ -58,7 +58,7 @@ pub struct JoinData<'a> { pub dt: &'a DeltaTime, pub controller: &'a Controller, pub inputs: &'a ControllerInputs, - pub stats: &'a Stats, + pub health: &'a Health, pub energy: &'a Energy, pub loadout: &'a Loadout, pub body: &'a Body, @@ -85,7 +85,7 @@ pub type JoinTuple<'a> = ( RestrictedMut<'a, Energy>, RestrictedMut<'a, Loadout>, &'a mut Controller, - &'a Stats, + &'a Health, &'a Body, &'a PhysicsState, Option<&'a Attacking>, @@ -123,7 +123,7 @@ impl<'a> JoinData<'a> { loadout: j.7.get_unchecked(), controller: j.8, inputs: &j.8.inputs, - stats: j.9, + health: j.9, body: j.10, physics: j.11, attacking: j.12, @@ -155,7 +155,7 @@ impl<'a> System<'a> for Sys { WriteStorage<'a, Energy>, WriteStorage<'a, Loadout>, WriteStorage<'a, Controller>, - ReadStorage<'a, Stats>, + ReadStorage<'a, Health>, ReadStorage<'a, Body>, ReadStorage<'a, PhysicsState>, ReadStorage<'a, Attacking>, @@ -182,7 +182,7 @@ impl<'a> System<'a> for Sys { mut energies, mut loadouts, mut controllers, - stats, + healths, bodies, physics_states, attacking_storage, @@ -206,7 +206,7 @@ impl<'a> System<'a> for Sys { &mut energies.restrict_mut(), &mut loadouts.restrict_mut(), &mut controllers, - &stats, + &healths, &bodies, &physics_states, attacking_storage.maybe(), diff --git a/common/src/sys/melee.rs b/common/src/sys/melee.rs index b974ca6bd4..93be75b795 100644 --- a/common/src/sys/melee.rs +++ b/common/src/sys/melee.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{buff, group, Attacking, Body, CharacterState, Loadout, Ori, Pos, Scale, Stats}, + comp::{buff, group, Attacking, Body, CharacterState, Health, Loadout, Ori, Pos, Scale}, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, span, @@ -28,7 +28,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Ori>, ReadStorage<'a, Scale>, ReadStorage<'a, Body>, - ReadStorage<'a, Stats>, + ReadStorage<'a, Health>, ReadStorage<'a, Loadout>, ReadStorage<'a, group::Group>, ReadStorage<'a, CharacterState>, @@ -47,7 +47,7 @@ impl<'a> System<'a> for Sys { orientations, scales, bodies, - stats, + healths, loadouts, groups, character_states, @@ -75,14 +75,14 @@ impl<'a> System<'a> for Sys { attack.applied = true; // Go through all other entities - for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, stats_b, body_b) in ( + for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, health_b, body_b) in ( &entities, &uids, &positions, &orientations, scales.maybe(), character_states.maybe(), - &stats, + &healths, &bodies, ) .join() @@ -99,7 +99,7 @@ impl<'a> System<'a> for Sys { // Check if it is a hit if entity != b - && !stats_b.is_dead + && !health_b.is_dead // Spherical wedge shaped attack field && pos.0.distance_squared(pos_b.0) < (rad_b + scale * attack.range).powi(2) && ori2.angle_between(pos_b2 - pos2) < attack.max_angle + (rad_b / pos2.distance(pos_b2)).atan() diff --git a/common/src/sys/shockwave.rs b/common/src/sys/shockwave.rs index 0da87146bf..f85fe7cce6 100644 --- a/common/src/sys/shockwave.rs +++ b/common/src/sys/shockwave.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - group, Body, CharacterState, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale, - Shockwave, ShockwaveHitEntities, Stats, + group, Body, CharacterState, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, + Scale, Shockwave, ShockwaveHitEntities, }, event::{EventBus, LocalEvent, ServerEvent}, state::{DeltaTime, Time}, @@ -31,7 +31,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Ori>, ReadStorage<'a, Scale>, ReadStorage<'a, Body>, - ReadStorage<'a, Stats>, + ReadStorage<'a, Health>, ReadStorage<'a, Loadout>, ReadStorage<'a, group::Group>, ReadStorage<'a, CharacterState>, @@ -55,7 +55,7 @@ impl<'a> System<'a> for Sys { orientations, scales, bodies, - stats, + healths, loadouts, groups, character_states, @@ -133,7 +133,7 @@ impl<'a> System<'a> for Sys { ori_b, scale_b_maybe, character_b, - stats_b, + health_b, body_b, physics_state_b, ) in ( @@ -145,7 +145,7 @@ impl<'a> System<'a> for Sys { &orientations, scales.maybe(), character_states.maybe(), - &stats, + &healths, &bodies, &physics_states, ) @@ -179,7 +179,7 @@ impl<'a> System<'a> for Sys { // Check if it is a hit let hit = entity != b - && !stats_b.is_dead + && !health_b.is_dead // Collision shapes && { // TODO: write code to collide rect with the arc strip so that we can do diff --git a/common/src/sys/stats.rs b/common/src/sys/stats.rs index bf05054cb6..e76a900e8c 100644 --- a/common/src/sys/stats.rs +++ b/common/src/sys/stats.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{CharacterState, Energy, EnergyChange, EnergySource, HealthSource, Stats}, + comp::{CharacterState, Energy, EnergyChange, EnergySource, Health, HealthSource, Stats}, event::{EventBus, ServerEvent}, metrics::SysMetrics, span, @@ -20,42 +20,59 @@ impl<'a> System<'a> for Sys { ReadExpect<'a, SysMetrics>, ReadStorage<'a, CharacterState>, WriteStorage<'a, Stats>, + WriteStorage<'a, Health>, WriteStorage<'a, Energy>, ); fn run( &mut self, - (entities, dt, server_event_bus, sys_metrics, character_states, mut stats, mut energies): Self::SystemData, + ( + entities, + dt, + server_event_bus, + sys_metrics, + character_states, + mut stats, + mut healths, + mut energies, + ): Self::SystemData, ) { let start_time = std::time::Instant::now(); span!(_guard, "run", "stats::Sys::run"); let mut server_event_emitter = server_event_bus.emitter(); // Increment last change timer - stats.set_event_emission(false); // avoid unnecessary syncing - for stat in (&mut stats).join() { - stat.health.last_change.0 += f64::from(dt.0); + healths.set_event_emission(false); // avoid unnecessary syncing + for health in (&mut healths).join() { + health.last_change.0 += f64::from(dt.0); } - stats.set_event_emission(true); + healths.set_event_emission(true); // Update stats - for (entity, mut stats) in (&entities, &mut stats.restrict_mut()).join() { + for (entity, mut stats, mut health) in ( + &entities, + &mut stats.restrict_mut(), + &mut healths.restrict_mut(), + ) + .join() + { let (set_dead, level_up) = { let stat = stats.get_unchecked(); + let health = health.get_unchecked(); ( - stat.should_die() && !stat.is_dead, + health.should_die() && !health.is_dead, stat.exp.current() >= stat.exp.maximum(), ) }; if set_dead { - let stat = stats.get_mut_unchecked(); + let health = health.get_mut_unchecked(); server_event_emitter.emit(ServerEvent::Destroy { entity, - cause: stat.health.last_change.1.cause, + cause: health.last_change.1.cause, }); - stat.is_dead = true; + health.is_dead = true; } if level_up { @@ -67,9 +84,9 @@ impl<'a> System<'a> for Sys { server_event_emitter.emit(ServerEvent::LevelUp(entity, stat.level.level())); } - stat.update_max_hp(stat.body_type); - stat.health - .set_to(stat.health.maximum(), HealthSource::LevelUp); + let health = health.get_mut_unchecked(); + health.update_max_hp(Some(stat.body_type), stat.level.level()); + health.set_to(health.maximum(), HealthSource::LevelUp); } } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 2db160010e..683915b11c 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -400,9 +400,9 @@ fn handle_kill( server .state .ecs_mut() - .write_storage::() + .write_storage::() .get_mut(target) - .map(|s| s.health.set_to(0, reason)); + .map(|h| h.set_to(0, reason)); } fn handle_time( @@ -471,13 +471,13 @@ fn handle_health( action: &ChatCommand, ) { if let Ok(hp) = scan_fmt!(&args, &action.arg_fmt(), u32) { - if let Some(stats) = server + if let Some(health) = server .state .ecs() - .write_storage::() + .write_storage::() .get_mut(target) { - stats.health.set_to(hp * 10, comp::HealthSource::Command); + health.set_to(hp * 10, comp::HealthSource::Command); } else { server.notify_client( client, @@ -656,6 +656,7 @@ fn handle_spawn( .create_npc( pos, comp::Stats::new(get_npc_name(id).into(), body), + comp::Health::new(body, 1), LoadoutBuilder::build_loadout(body, alignment, None, false) .build(), body, @@ -762,9 +763,11 @@ fn handle_spawn_training_dummy( // Level 0 will prevent exp gain from kill stats.level.set_level(0); + let health = comp::Health::new(body, 0); + server .state - .create_npc(pos, stats, comp::Loadout::default(), body) + .create_npc(pos, stats, health, comp::Loadout::default(), body) .with(comp::Vel(vel)) .with(comp::MountState::Unmounted) .build(); @@ -924,12 +927,12 @@ fn handle_kill_npcs( _action: &ChatCommand, ) { let ecs = server.state.ecs(); - let mut stats = ecs.write_storage::(); + let mut healths = ecs.write_storage::(); let players = ecs.read_storage::(); let mut count = 0; - for (stats, ()) in (&mut stats, !&players).join() { + for (health, ()) in (&mut healths, !&players).join() { count += 1; - stats.health.set_to(0, comp::HealthSource::Command); + health.set_to(0, comp::HealthSource::Command); } let text = if count > 0 { format!("Destroyed {} NPCs.", count) @@ -1702,6 +1705,8 @@ fn handle_set_level( PlayerListUpdate::LevelChange(uid, lvl), )); + let body_type: Option; + if let Some(stats) = server .state .ecs_mut() @@ -1709,13 +1714,20 @@ fn handle_set_level( .get_mut(player) { stats.level.set_level(lvl); - - stats.update_max_hp(stats.body_type); - stats - .health - .set_to(stats.health.maximum(), comp::HealthSource::LevelUp); + body_type = Some(stats.body_type); } else { error_msg = Some(ChatType::CommandError.server_msg("Player has no stats!")); + body_type = None; + } + + if let Some(health) = server + .state + .ecs_mut() + .write_storage::() + .get_mut(player) + { + health.update_max_hp(body_type, lvl); + health.set_to(health.maximum(), comp::HealthSource::LevelUp); } }, Err(e) => { diff --git a/server/src/events/entity_creation.rs b/server/src/events/entity_creation.rs index 9f52028e5c..5126406c04 100644 --- a/server/src/events/entity_creation.rs +++ b/server/src/events/entity_creation.rs @@ -3,8 +3,8 @@ use common::{ character::CharacterId, comp::{ self, beam, humanoid::DEFAULT_HUMANOID_EYE_HEIGHT, shockwave, Agent, Alignment, Body, - Gravity, Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, Vel, - WaypointArea, + Gravity, Health, Item, ItemDrop, LightEmitter, Loadout, Ori, Pos, Projectile, Scale, Stats, + Vel, WaypointArea, }, outcome::Outcome, util::Dir, @@ -37,6 +37,7 @@ pub fn handle_create_npc( server: &mut Server, pos: Pos, stats: Stats, + health: Health, loadout: Loadout, body: Body, agent: impl Into>, @@ -55,7 +56,7 @@ pub fn handle_create_npc( let entity = server .state - .create_npc(pos, stats, loadout, body) + .create_npc(pos, stats, health, loadout, body) .with(scale) .with(alignment); diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 2f0e36269e..e38107577f 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -8,8 +8,8 @@ use common::{ comp::{ self, buff, chat::{KillSource, KillType}, - object, Alignment, Body, Energy, EnergyChange, Group, HealthChange, HealthSource, Item, - Player, Pos, Stats, + object, Alignment, Body, Energy, EnergyChange, Group, Health, HealthChange, HealthSource, + Item, Player, Pos, Stats, }, lottery::Lottery, msg::{PlayerListUpdate, ServerGeneral}, @@ -28,11 +28,10 @@ use tracing::error; use vek::Vec3; pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) { - let state = &server.state; - let ecs = state.ecs(); + let ecs = &server.state.ecs(); if let Some(entity) = ecs.entity_from_uid(uid.into()) { - if let Some(stats) = ecs.write_storage::().get_mut(entity) { - stats.health.change_by(change); + if let Some(health) = ecs.write_storage::().get_mut(entity) { + health.change_by(change); } } } @@ -453,7 +452,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) { let state = &server.state; if vel.z <= -30.0 { - if let Some(stats) = state.ecs().write_storage::().get_mut(entity) { + if let Some(health) = state.ecs().write_storage::().get_mut(entity) { let falldmg = (vel.z.powi(2) / 20.0 - 40.0) * 10.0; let damage = Damage { source: DamageSource::Falling, @@ -461,7 +460,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) }; let loadouts = state.ecs().read_storage::(); let change = damage.modify_damage(false, loadouts.get(entity), None); - stats.health.change_by(change); + health.change_by(change); } } } @@ -483,9 +482,9 @@ pub fn handle_respawn(server: &Server, entity: EcsEntity) { state .ecs() - .write_storage::() + .write_storage::() .get_mut(entity) - .map(|stats| stats.revive()); + .map(|health| health.revive()); state .ecs() .write_storage::() @@ -550,19 +549,19 @@ pub fn handle_explosion( for effect in explosion.effects { match effect { RadiusEffect::Damages(damages) => { - for (entity_b, pos_b, ori_b, character_b, stats_b, loadout_b) in ( + for (entity_b, pos_b, ori_b, character_b, health_b, loadout_b) in ( &ecs.entities(), &ecs.read_storage::(), &ecs.read_storage::(), ecs.read_storage::().maybe(), - &mut ecs.write_storage::(), + &mut ecs.write_storage::(), ecs.read_storage::().maybe(), ) .join() { let distance_squared = pos.distance_squared(pos_b.0); // Check if it is a hit - if !stats_b.is_dead + if !health_b.is_dead // RADIUS && distance_squared < explosion.radius.powi(2) { @@ -592,7 +591,7 @@ pub fn handle_explosion( let change = damage.modify_damage(block, loadout_b, owner); if change.amount != 0 { - stats_b.health.change_by(change); + health_b.change_by(change); if let Some(owner) = owner_entity { if let Some(energy) = ecs.write_storage::().get_mut(owner) diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index a9b936da70..f6b517321a 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -67,10 +67,10 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv return; }; - // Grab the stats from the player and check if the player is dead. - let stats = state.ecs().read_storage::(); - if let Some(entity_stats) = stats.get(entity) { - if entity_stats.is_dead { + // Grab the health from the player and check if the player is dead. + let healths = state.ecs().read_storage::(); + if let Some(entity_health) = healths.get(entity) { + if entity_health.is_dead { debug!("Failed to pick up item as the player is dead"); return; // If dead, don't continue } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 358363c4a5..7c083d8be0 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -109,6 +109,7 @@ impl Server { ServerEvent::CreateNpc { pos, stats, + health, loadout, body, agent, @@ -116,7 +117,7 @@ impl Server { scale, drop_item, } => handle_create_npc( - self, pos, stats, loadout, body, agent, alignment, scale, drop_item, + self, pos, stats, health, loadout, body, agent, alignment, scale, drop_item, ), ServerEvent::CreateWaypoint(pos) => handle_create_waypoint(self, pos), ServerEvent::ClientDisconnect(entity) => { diff --git a/server/src/persistence/character/conversions.rs b/server/src/persistence/character/conversions.rs index fea9f8af05..79f2d6093c 100644 --- a/server/src/persistence/character/conversions.rs +++ b/server/src/persistence/character/conversions.rs @@ -318,11 +318,11 @@ pub fn convert_stats_from_database(stats: &Stats, alias: String) -> common::comp new_stats.level.set_level(stats.level as u32); new_stats.exp.update_maximum(stats.level as u32); new_stats.exp.set_current(stats.exp as u32); - new_stats.update_max_hp(new_stats.body_type); + /*new_stats.update_max_hp(new_stats.body_type); new_stats.health.set_to( new_stats.health.maximum(), common::comp::HealthSource::Revive, - ); + );*/ new_stats.endurance = stats.endurance as u32; new_stats.fitness = stats.fitness as u32; new_stats.willpower = stats.willpower as u32; diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 46fe38fb12..cd806f88f6 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -26,6 +26,7 @@ pub trait StateExt { &mut self, pos: comp::Pos, stats: comp::Stats, + health: comp::Health, loadout: comp::Loadout, body: comp::Body, ) -> EcsEntityBuilder; @@ -74,9 +75,9 @@ impl StateExt for State { match effect { Effect::Health(change) => { self.ecs() - .write_storage::() + .write_storage::() .get_mut(entity) - .map(|stats| stats.health.change_by(change)); + .map(|health| health.change_by(change)); }, Effect::Xp(xp) => { self.ecs() @@ -91,6 +92,7 @@ impl StateExt for State { &mut self, pos: comp::Pos, stats: comp::Stats, + health: comp::Health, loadout: comp::Loadout, body: comp::Body, ) -> EcsEntityBuilder { @@ -107,6 +109,7 @@ impl StateExt for State { .with(comp::Controller::default()) .with(body) .with(stats) + .with(health) .with(comp::Alignment::Npc) .with(comp::Energy::new(body.base_energy())) .with(comp::Gravity(1.0)) diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs new file mode 100644 index 0000000000..5fdb00985d --- /dev/null +++ b/server/src/sys/message.rs @@ -0,0 +1,743 @@ +use super::SysTimer; +use crate::{ + alias_validator::AliasValidator, + character_creator, + client::Client, + login_provider::LoginProvider, + metrics::{NetworkRequestMetrics, PlayerMetrics}, + persistence::character_loader::CharacterLoader, + EditableSettings, Settings, +}; +use common::{ + comp::{ + Admin, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Health, Ori, + Player, Pos, Stats, UnresolvedChatMsg, Vel, + }, + event::{EventBus, ServerEvent}, + msg::{ + validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientGeneral, ClientInGame, + ClientRegister, DisconnectReason, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, + ServerGeneral, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, + }, + span, + state::{BlockChange, Time}, + sync::Uid, + terrain::{TerrainChunkSize, TerrainGrid}, + vol::{ReadVol, RectVolSize}, +}; +use futures_executor::block_on; +use futures_timer::Delay; +use futures_util::{select, FutureExt}; +use hashbrown::HashMap; +use specs::{ + Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage, +}; +use tracing::{debug, error, info, trace, warn}; + +impl Sys { + #[allow(clippy::too_many_arguments)] + fn handle_client_msg( + server_emitter: &mut common::event::Emitter<'_, ServerEvent>, + new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, + entity: specs::Entity, + client: &mut Client, + player_metrics: &ReadExpect<'_, PlayerMetrics>, + uids: &ReadStorage<'_, Uid>, + chat_modes: &ReadStorage<'_, ChatMode>, + msg: ClientGeneral, + ) -> Result<(), crate::error::Error> { + match msg { + ClientGeneral::ChatMsg(message) => { + if client.registered { + match validate_chat_msg(&message) { + Ok(()) => { + if let Some(from) = uids.get(entity) { + let mode = chat_modes.get(entity).cloned().unwrap_or_default(); + let msg = mode.new_message(*from, message); + new_chat_msgs.push((Some(entity), msg)); + } else { + error!("Could not send message. Missing player uid"); + } + }, + Err(ChatMsgValidationError::TooLong) => { + let max = MAX_BYTES_CHAT_MSG; + let len = message.len(); + warn!(?len, ?max, "Received a chat message that's too long") + }, + } + } + }, + ClientGeneral::Disconnect => { + client.send_msg(ServerGeneral::Disconnect(DisconnectReason::Requested)); + }, + ClientGeneral::Terminate => { + debug!(?entity, "Client send message to termitate session"); + player_metrics + .clients_disconnected + .with_label_values(&["gracefully"]) + .inc(); + server_emitter.emit(ServerEvent::ClientDisconnect(entity)); + }, + _ => unreachable!("not a client_general msg"), + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_client_in_game_msg( + server_emitter: &mut common::event::Emitter<'_, ServerEvent>, + entity: specs::Entity, + client: &mut Client, + terrain: &ReadExpect<'_, TerrainGrid>, + network_metrics: &ReadExpect<'_, NetworkRequestMetrics>, + can_build: &ReadStorage<'_, CanBuild>, + force_updates: &ReadStorage<'_, ForceUpdate>, + stats: &mut WriteStorage<'_, Stats>, + healths: &mut WriteStorage<'_, Health>, + block_changes: &mut Write<'_, BlockChange>, + positions: &mut WriteStorage<'_, Pos>, + velocities: &mut WriteStorage<'_, Vel>, + orientations: &mut WriteStorage<'_, Ori>, + players: &mut WriteStorage<'_, Player>, + controllers: &mut WriteStorage<'_, Controller>, + settings: &Read<'_, Settings>, + msg: ClientGeneral, + ) -> Result<(), crate::error::Error> { + if client.in_game.is_none() { + debug!(?entity, "client is not in_game, ignoring msg"); + trace!(?msg, "ignored msg content"); + if matches!(msg, ClientGeneral::TerrainChunkRequest{ .. }) { + network_metrics.chunks_request_dropped.inc(); + } + return Ok(()); + } + match msg { + // Go back to registered state (char selection screen) + ClientGeneral::ExitInGame => { + client.in_game = None; + server_emitter.emit(ServerEvent::ExitIngame { entity }); + client.send_msg(ServerGeneral::ExitInGameSuccess); + }, + ClientGeneral::SetViewDistance(view_distance) => { + players.get_mut(entity).map(|player| { + player.view_distance = Some( + settings + .max_view_distance + .map(|max| view_distance.min(max)) + .unwrap_or(view_distance), + ) + }); + + //correct client if its VD is to high + if settings + .max_view_distance + .map(|max| view_distance > max) + .unwrap_or(false) + { + client.send_msg(ServerGeneral::SetViewDistance( + settings.max_view_distance.unwrap_or(0), + )); + } + }, + ClientGeneral::ControllerInputs(inputs) => { + if let Some(ClientInGame::Character) = client.in_game { + if let Some(controller) = controllers.get_mut(entity) { + controller.inputs.update_with_new(inputs); + } + } + }, + ClientGeneral::ControlEvent(event) => { + if let Some(ClientInGame::Character) = client.in_game { + // Skip respawn if client entity is alive + if let ControlEvent::Respawn = event { + if healths.get(entity).map_or(true, |h| !h.is_dead) { + //Todo: comment why return! + return Ok(()); + } + } + if let Some(controller) = controllers.get_mut(entity) { + controller.events.push(event); + } + } + }, + ClientGeneral::ControlAction(event) => { + if let Some(ClientInGame::Character) = client.in_game { + if let Some(controller) = controllers.get_mut(entity) { + controller.actions.push(event); + } + } + }, + ClientGeneral::PlayerPhysics { pos, vel, ori } => { + if let Some(ClientInGame::Character) = client.in_game { + if force_updates.get(entity).is_none() + && healths.get(entity).map_or(true, |h| !h.is_dead) + { + let _ = positions.insert(entity, pos); + let _ = velocities.insert(entity, vel); + let _ = orientations.insert(entity, ori); + } + } + }, + ClientGeneral::BreakBlock(pos) => { + if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { + block_changes.set(pos, block.into_vacant()); + } + }, + ClientGeneral::PlaceBlock(pos, block) => { + if can_build.get(entity).is_some() { + block_changes.try_set(pos, block); + } + }, + ClientGeneral::TerrainChunkRequest { key } => { + let in_vd = if let (Some(view_distance), Some(pos)) = ( + players.get(entity).and_then(|p| p.view_distance), + positions.get(entity), + ) { + pos.0.xy().map(|e| e as f64).distance( + key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64), + ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt()) + * TerrainChunkSize::RECT_SIZE.x as f64 + } else { + true + }; + if in_vd { + match terrain.get_key(key) { + Some(chunk) => { + network_metrics.chunks_served_from_memory.inc(); + client.send_msg(ServerGeneral::TerrainChunkUpdate { + key, + chunk: Ok(Box::new(chunk.clone())), + }) + }, + None => { + network_metrics.chunks_generation_triggered.inc(); + server_emitter.emit(ServerEvent::ChunkRequest(entity, key)) + }, + } + } else { + network_metrics.chunks_request_dropped.inc(); + } + }, + ClientGeneral::UnlockSkill(skill) => { + stats + .get_mut(entity) + .map(|s| s.skill_set.unlock_skill(skill)); + }, + ClientGeneral::RefundSkill(skill) => { + stats + .get_mut(entity) + .map(|s| s.skill_set.refund_skill(skill)); + }, + ClientGeneral::UnlockSkillGroup(skill_group_type) => { + stats + .get_mut(entity) + .map(|s| s.skill_set.unlock_skill_group(skill_group_type)); + }, + _ => unreachable!("not a client_in_game msg"), + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_client_character_screen_msg( + server_emitter: &mut common::event::Emitter<'_, ServerEvent>, + new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, + entity: specs::Entity, + client: &mut Client, + character_loader: &ReadExpect<'_, CharacterLoader>, + uids: &ReadStorage<'_, Uid>, + players: &mut WriteStorage<'_, Player>, + editable_settings: &ReadExpect<'_, EditableSettings>, + alias_validator: &ReadExpect<'_, AliasValidator>, + msg: ClientGeneral, + ) -> Result<(), crate::error::Error> { + match msg { + // Request spectator state + ClientGeneral::Spectate if client.registered => { + client.in_game = Some(ClientInGame::Spectator) + }, + ClientGeneral::Spectate => debug!("dropped Spectate msg from unregistered client"), + ClientGeneral::Character(character_id) + if client.registered && client.in_game.is_none() => + { + if let Some(player) = players.get(entity) { + // Send a request to load the character's component data from the + // DB. Once loaded, persisted components such as stats and inventory + // will be inserted for the entity + character_loader.load_character_data( + entity, + player.uuid().to_string(), + character_id, + ); + + // Start inserting non-persisted/default components for the entity + // while we load the DB data + server_emitter.emit(ServerEvent::InitCharacterData { + entity, + character_id, + }); + + // Give the player a welcome message + if !editable_settings.server_description.is_empty() { + client.send_msg( + ChatType::CommandInfo + .server_msg(String::from(&*editable_settings.server_description)), + ); + } + + if !client.login_msg_sent { + if let Some(player_uid) = uids.get(entity) { + new_chat_msgs.push((None, UnresolvedChatMsg { + chat_type: ChatType::Online(*player_uid), + message: "".to_string(), + })); + + client.login_msg_sent = true; + } + } + } else { + client.send_msg(ServerGeneral::CharacterDataLoadError(String::from( + "Failed to fetch player entity", + ))) + } + } + ClientGeneral::Character(_) => { + let registered = client.registered; + let in_game = client.in_game; + debug!(?registered, ?in_game, "dropped Character msg from client"); + }, + ClientGeneral::RequestCharacterList => { + if let Some(player) = players.get(entity) { + character_loader.load_character_list(entity, player.uuid().to_string()) + } + }, + ClientGeneral::CreateCharacter { alias, tool, body } => { + if let Err(error) = alias_validator.validate(&alias) { + debug!(?error, ?alias, "denied alias as it contained a banned word"); + client.send_msg(ServerGeneral::CharacterActionError(error.to_string())); + } else if let Some(player) = players.get(entity) { + character_creator::create_character( + entity, + player.uuid().to_string(), + alias, + tool, + body, + character_loader, + ); + } + }, + ClientGeneral::DeleteCharacter(character_id) => { + if let Some(player) = players.get(entity) { + character_loader.delete_character( + entity, + player.uuid().to_string(), + character_id, + ); + } + }, + _ => unreachable!("not a client_character_screen msg"), + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> { + match msg { + PingMsg::Ping => client.send_msg(PingMsg::Pong), + PingMsg::Pong => {}, + } + Ok(()) + } + + #[allow(clippy::too_many_arguments)] + fn handle_register_msg( + player_list: &HashMap, + new_players: &mut Vec, + entity: specs::Entity, + client: &mut Client, + player_metrics: &ReadExpect<'_, PlayerMetrics>, + login_provider: &mut WriteExpect<'_, LoginProvider>, + admins: &mut WriteStorage<'_, Admin>, + players: &mut WriteStorage<'_, Player>, + editable_settings: &ReadExpect<'_, EditableSettings>, + msg: ClientRegister, + ) -> Result<(), crate::error::Error> { + let (username, uuid) = match login_provider.try_login( + &msg.token_or_username, + &*editable_settings.admins, + &*editable_settings.whitelist, + &*editable_settings.banlist, + ) { + Err(err) => { + client + .register_stream + .send(ServerRegisterAnswer::Err(err))?; + return Ok(()); + }, + Ok((username, uuid)) => (username, uuid), + }; + + const INITIAL_VD: Option = Some(5); //will be changed after login + let player = Player::new(username, None, INITIAL_VD, uuid); + let is_admin = editable_settings.admins.contains(&uuid); + + if !player.is_valid() { + // Invalid player + client + .register_stream + .send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?; + return Ok(()); + } + + if !client.registered && client.in_game.is_none() { + // Add Player component to this client + let _ = players.insert(entity, player); + player_metrics.players_connected.inc(); + + // Give the Admin component to the player if their name exists in + // admin list + if is_admin { + let _ = admins.insert(entity, Admin); + } + + // Tell the client its request was successful. + client.registered = true; + client.register_stream.send(ServerRegisterAnswer::Ok(()))?; + + // Send initial player list + client.send_msg(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init( + player_list.clone(), + ))); + + // Add to list to notify all clients of the new player + new_players.push(entity); + } + Ok(()) + } + + ///We needed to move this to a async fn, if we would use a async closures + /// the compiler generates to much recursion and fails to compile this + #[allow(clippy::too_many_arguments)] + async fn handle_messages( + server_emitter: &mut common::event::Emitter<'_, ServerEvent>, + new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, + player_list: &HashMap, + new_players: &mut Vec, + entity: specs::Entity, + client: &mut Client, + cnt: &mut u64, + character_loader: &ReadExpect<'_, CharacterLoader>, + terrain: &ReadExpect<'_, TerrainGrid>, + network_metrics: &ReadExpect<'_, NetworkRequestMetrics>, + player_metrics: &ReadExpect<'_, PlayerMetrics>, + uids: &ReadStorage<'_, Uid>, + can_build: &ReadStorage<'_, CanBuild>, + force_updates: &ReadStorage<'_, ForceUpdate>, + stats: &mut WriteStorage<'_, Stats>, + healths: &mut WriteStorage<'_, Health>, + chat_modes: &ReadStorage<'_, ChatMode>, + login_provider: &mut WriteExpect<'_, LoginProvider>, + block_changes: &mut Write<'_, BlockChange>, + admins: &mut WriteStorage<'_, Admin>, + positions: &mut WriteStorage<'_, Pos>, + velocities: &mut WriteStorage<'_, Vel>, + orientations: &mut WriteStorage<'_, Ori>, + players: &mut WriteStorage<'_, Player>, + controllers: &mut WriteStorage<'_, Controller>, + settings: &Read<'_, Settings>, + editable_settings: &ReadExpect<'_, EditableSettings>, + alias_validator: &ReadExpect<'_, AliasValidator>, + ) -> Result<(), crate::error::Error> { + let (mut b1, mut b2, mut b3, mut b4, mut b5) = ( + client.network_error, + client.network_error, + client.network_error, + client.network_error, + client.network_error, + ); + loop { + /* + waiting for 1 of the 5 streams to return a massage asynchronous. + If so, handle that msg type. This code will be refactored soon + */ + + let q1 = Client::internal_recv(&mut b1, &mut client.general_stream); + let q2 = Client::internal_recv(&mut b2, &mut client.in_game_stream); + let q3 = Client::internal_recv(&mut b3, &mut client.character_screen_stream); + let q4 = Client::internal_recv(&mut b4, &mut client.ping_stream); + let q5 = Client::internal_recv(&mut b5, &mut client.register_stream); + + let (m1, m2, m3, m4, m5) = select!( + msg = q1.fuse() => (Some(msg), None, None, None, None), + msg = q2.fuse() => (None, Some(msg), None, None, None), + msg = q3.fuse() => (None, None, Some(msg), None, None), + msg = q4.fuse() => (None, None, None, Some(msg), None), + msg = q5.fuse() => (None, None, None, None,Some(msg)), + ); + *cnt += 1; + if let Some(msg) = m1 { + client.network_error |= b1; + Self::handle_client_msg( + server_emitter, + new_chat_msgs, + entity, + client, + player_metrics, + uids, + chat_modes, + msg?, + )?; + } + if let Some(msg) = m2 { + client.network_error |= b2; + Self::handle_client_in_game_msg( + server_emitter, + entity, + client, + terrain, + network_metrics, + can_build, + force_updates, + stats, + healths, + block_changes, + positions, + velocities, + orientations, + players, + controllers, + settings, + msg?, + )?; + } + if let Some(msg) = m3 { + client.network_error |= b3; + Self::handle_client_character_screen_msg( + server_emitter, + new_chat_msgs, + entity, + client, + character_loader, + uids, + players, + editable_settings, + alias_validator, + msg?, + )?; + } + if let Some(msg) = m4 { + client.network_error |= b4; + Self::handle_ping_msg(client, msg?)?; + } + if let Some(msg) = m5 { + client.network_error |= b5; + Self::handle_register_msg( + player_list, + new_players, + entity, + client, + player_metrics, + login_provider, + admins, + players, + editable_settings, + msg?, + )?; + } + } + } +} + +/// This system will handle new messages from clients +pub struct Sys; +impl<'a> System<'a> for Sys { + #[allow(clippy::type_complexity)] // TODO: Pending review in #587 + type SystemData = ( + Entities<'a>, + Read<'a, EventBus>, + Read<'a, Time>, + ReadExpect<'a, CharacterLoader>, + ReadExpect<'a, TerrainGrid>, + ReadExpect<'a, NetworkRequestMetrics>, + ReadExpect<'a, PlayerMetrics>, + Write<'a, SysTimer>, + ReadStorage<'a, Uid>, + ReadStorage<'a, CanBuild>, + ReadStorage<'a, ForceUpdate>, + WriteStorage<'a, Stats>, + WriteStorage<'a, Health>, + ReadStorage<'a, ChatMode>, + WriteExpect<'a, LoginProvider>, + Write<'a, BlockChange>, + WriteStorage<'a, Admin>, + WriteStorage<'a, Pos>, + WriteStorage<'a, Vel>, + WriteStorage<'a, Ori>, + WriteStorage<'a, Player>, + WriteStorage<'a, Client>, + WriteStorage<'a, Controller>, + Read<'a, Settings>, + ReadExpect<'a, EditableSettings>, + ReadExpect<'a, AliasValidator>, + ); + + #[allow(clippy::match_ref_pats)] // TODO: Pending review in #587 + #[allow(clippy::single_char_pattern)] // TODO: Pending review in #587 + #[allow(clippy::single_match)] // TODO: Pending review in #587 + fn run( + &mut self, + ( + entities, + server_event_bus, + time, + character_loader, + terrain, + network_metrics, + player_metrics, + mut timer, + uids, + can_build, + force_updates, + mut stats, + mut healths, + chat_modes, + mut accounts, + mut block_changes, + mut admins, + mut positions, + mut velocities, + mut orientations, + mut players, + mut clients, + mut controllers, + settings, + editable_settings, + alias_validator, + ): Self::SystemData, + ) { + span!(_guard, "run", "message::Sys::run"); + timer.start(); + + let mut server_emitter = server_event_bus.emitter(); + + let mut new_chat_msgs = Vec::new(); + + // Player list to send new players. + let player_list = (&uids, &players, stats.maybe(), admins.maybe()) + .join() + .map(|(uid, player, stats, admin)| { + (*uid, PlayerInfo { + is_online: true, + is_admin: admin.is_some(), + player_alias: player.alias.clone(), + character: stats.map(|stats| CharacterInfo { + name: stats.name.clone(), + level: stats.level.level(), + }), + }) + }) + .collect::>(); + // List of new players to update player lists of all clients. + let mut new_players = Vec::new(); + + for (entity, client) in (&entities, &mut clients).join() { + let mut cnt = 0; + + let network_err: Result<(), crate::error::Error> = block_on(async { + //TIMEOUT 0.02 ms for msg handling + let work_future = Self::handle_messages( + &mut server_emitter, + &mut new_chat_msgs, + &player_list, + &mut new_players, + entity, + client, + &mut cnt, + &character_loader, + &terrain, + &network_metrics, + &player_metrics, + &uids, + &can_build, + &force_updates, + &mut stats, + &mut healths, + &chat_modes, + &mut accounts, + &mut block_changes, + &mut admins, + &mut positions, + &mut velocities, + &mut orientations, + &mut players, + &mut controllers, + &settings, + &editable_settings, + &alias_validator, + ); + select!( + _ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()), + err = work_future.fuse() => err, + ) + }); + + // Network error + if network_err.is_err() { + debug!(?entity, "postbox error with client, disconnecting"); + player_metrics + .clients_disconnected + .with_label_values(&["network_error"]) + .inc(); + server_emitter.emit(ServerEvent::ClientDisconnect(entity)); + } else if cnt > 0 { + // Update client ping. + client.last_ping = time.0 + } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 + // Timeout + { + info!(?entity, "timeout error with client, disconnecting"); + player_metrics + .clients_disconnected + .with_label_values(&["timeout"]) + .inc(); + server_emitter.emit(ServerEvent::ClientDisconnect(entity)); + } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 { + // Try pinging the client if the timeout is nearing. + client.send_msg(PingMsg::Ping); + } + } + + // Handle new players. + // Tell all clients to add them to the player list. + for entity in new_players { + if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { + let msg = + ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { + player_alias: player.alias.clone(), + is_online: true, + is_admin: admins.get(entity).is_some(), + character: None, // new players will be on character select. + })); + for client in (&mut clients).join().filter(|c| c.registered) { + client.send_msg(msg.clone()) + } + } + } + + // Handle new chat messages. + for (entity, msg) in new_chat_msgs { + // Handle chat commands. + if msg.message.starts_with("/") { + if let (Some(entity), true) = (entity, msg.message.len() > 1) { + let argv = String::from(&msg.message[1..]); + server_emitter.emit(ServerEvent::ChatCmd(entity, argv)); + } + } else { + // Send chat message + server_emitter.emit(ServerEvent::Chat(msg)); + } + } + + timer.end() + } +} diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index ae49d6d8e2..c7a3bcf644 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -1,7 +1,7 @@ use super::super::SysTimer; use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence, Settings}; use common::{ - comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Ori, Pos, Stats, Vel}, + comp::{CanBuild, ControlEvent, Controller, ForceUpdate, Health, Ori, Pos, Stats, Vel}, event::{EventBus, ServerEvent}, msg::{ClientGeneral, PresenceKind, ServerGeneral}, span, @@ -24,6 +24,7 @@ impl Sys { can_build: &ReadStorage<'_, CanBuild>, force_updates: &ReadStorage<'_, ForceUpdate>, stats: &mut WriteStorage<'_, Stats>, + healths: &ReadStorage<'_, Health>, block_changes: &mut Write<'_, BlockChange>, positions: &mut WriteStorage<'_, Pos>, velocities: &mut WriteStorage<'_, Vel>, @@ -78,7 +79,7 @@ impl Sys { if matches!(presence.kind, PresenceKind::Character(_)) { // Skip respawn if client entity is alive if let ControlEvent::Respawn = event { - if stats.get(entity).map_or(true, |s| !s.is_dead) { + if healths.get(entity).map_or(true, |h| !h.is_dead) { //Todo: comment why return! return Ok(()); } @@ -98,7 +99,7 @@ impl Sys { ClientGeneral::PlayerPhysics { pos, vel, ori } => { if matches!(presence.kind, PresenceKind::Character(_)) && force_updates.get(entity).is_none() - && stats.get(entity).map_or(true, |s| !s.is_dead) + && healths.get(entity).map_or(true, |h| !h.is_dead) { let _ = positions.insert(entity, pos); let _ = velocities.insert(entity, vel); @@ -176,6 +177,7 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, CanBuild>, ReadStorage<'a, ForceUpdate>, WriteStorage<'a, Stats>, + ReadStorage<'a, Health>, Write<'a, BlockChange>, WriteStorage<'a, Pos>, WriteStorage<'a, Vel>, @@ -197,6 +199,7 @@ impl<'a> System<'a> for Sys { can_build, force_updates, mut stats, + healths, mut block_changes, mut positions, mut velocities, @@ -226,6 +229,7 @@ impl<'a> System<'a> for Sys { &can_build, &force_updates, &mut stats, + &healths, &mut block_changes, &mut positions, &mut velocities, diff --git a/server/src/sys/sentinel.rs b/server/src/sys/sentinel.rs index 68d568b66c..a61dc65b6f 100644 --- a/server/src/sys/sentinel.rs +++ b/server/src/sys/sentinel.rs @@ -1,9 +1,9 @@ use super::SysTimer; use common::{ comp::{ - BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Energy, Gravity, Group, Item, - LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, Shockwave, - Stats, Sticky, Vel, + BeamSegment, Body, Buffs, CanBuild, CharacterState, Collider, Energy, Gravity, Group, + Health, Item, LightEmitter, Loadout, Mass, MountState, Mounting, Ori, Player, Pos, Scale, + Shockwave, Stats, Sticky, Vel, }, msg::EcsCompPacket, span, @@ -46,6 +46,7 @@ pub struct TrackedComps<'a> { pub stats: ReadStorage<'a, Stats>, pub buffs: ReadStorage<'a, Buffs>, pub energy: ReadStorage<'a, Energy>, + pub health: ReadStorage<'a, Health>, pub can_build: ReadStorage<'a, CanBuild>, pub light_emitter: ReadStorage<'a, LightEmitter>, pub item: ReadStorage<'a, Item>, @@ -94,6 +95,10 @@ impl<'a> TrackedComps<'a> { .get(entity) .cloned() .map(|c| comps.push(c.into())); + self.health + .get(entity) + .cloned() + .map(|c| comps.push(c.into())); self.can_build .get(entity) .cloned() @@ -164,6 +169,7 @@ pub struct ReadTrackers<'a> { pub stats: ReadExpect<'a, UpdateTracker>, pub buffs: ReadExpect<'a, UpdateTracker>, pub energy: ReadExpect<'a, UpdateTracker>, + pub health: ReadExpect<'a, UpdateTracker>, pub can_build: ReadExpect<'a, UpdateTracker>, pub light_emitter: ReadExpect<'a, UpdateTracker>, pub item: ReadExpect<'a, UpdateTracker>, @@ -195,6 +201,7 @@ impl<'a> ReadTrackers<'a> { .with_component(&comps.uid, &*self.stats, &comps.stats, filter) .with_component(&comps.uid, &*self.buffs, &comps.buffs, filter) .with_component(&comps.uid, &*self.energy, &comps.energy, filter) + .with_component(&comps.uid, &*self.health, &comps.health, filter) .with_component(&comps.uid, &*self.can_build, &comps.can_build, filter) .with_component( &comps.uid, @@ -233,6 +240,7 @@ pub struct WriteTrackers<'a> { stats: WriteExpect<'a, UpdateTracker>, buffs: WriteExpect<'a, UpdateTracker>, energy: WriteExpect<'a, UpdateTracker>, + health: WriteExpect<'a, UpdateTracker>, can_build: WriteExpect<'a, UpdateTracker>, light_emitter: WriteExpect<'a, UpdateTracker>, item: WriteExpect<'a, UpdateTracker>, @@ -258,6 +266,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { trackers.stats.record_changes(&comps.stats); trackers.buffs.record_changes(&comps.buffs); trackers.energy.record_changes(&comps.energy); + trackers.health.record_changes(&comps.health); trackers.can_build.record_changes(&comps.can_build); trackers.light_emitter.record_changes(&comps.light_emitter); trackers.item.record_changes(&comps.item); @@ -296,6 +305,7 @@ fn record_changes(comps: &TrackedComps, trackers: &mut WriteTrackers) { log_counts!(player, "Players"); log_counts!(stats, "Stats"); log_counts!(energy, "Energies"); + log_vounts!(health, "Healths"); log_counts!(light_emitter, "Light emitters"); log_counts!(item, "Items"); log_counts!(scale, "Scales"); @@ -319,6 +329,7 @@ pub fn register_trackers(world: &mut World) { world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); + world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); world.register_tracker::(); diff --git a/server/src/sys/terrain.rs b/server/src/sys/terrain.rs index b2da237a3c..ec12b0bb34 100644 --- a/server/src/sys/terrain.rs +++ b/server/src/sys/terrain.rs @@ -146,11 +146,7 @@ impl<'a> System<'a> for Sys { LoadoutBuilder::build_loadout(body, alignment, main_tool, entity.is_giant) .build(); - stats.update_max_hp(stats.body_type); - - stats - .health - .set_to(stats.health.maximum(), comp::HealthSource::Revive); + let health = comp::Health::new(stats.body_type, stats.level.level()); let can_speak = match body { comp::Body::Humanoid(_) => alignment == comp::Alignment::Npc, @@ -174,6 +170,7 @@ impl<'a> System<'a> for Sys { server_emitter.emit(ServerEvent::CreateNpc { pos: Pos(entity.pos), stats, + health, loadout, agent: if entity.has_agency { Some(comp::Agent::new(entity.pos, can_speak, &body)) diff --git a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs index 448c976f6b..5a01451d15 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/tests.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/tests.rs @@ -143,6 +143,7 @@ fn matches_ability_stage() { speed_increase: 0.05, max_speed_increase: 1.8, is_interruptible: true, + ability_key: states::utils::AbilityKey::Mouse1, }, stage: 1, combo: 0, @@ -203,6 +204,7 @@ fn ignores_different_ability_stage() { speed_increase: 0.05, max_speed_increase: 1.8, is_interruptible: true, + ability_key: states::utils::AbilityKey::Mouse1, }, stage: 1, combo: 0, diff --git a/voxygen/src/ecs/sys/floater.rs b/voxygen/src/ecs/sys/floater.rs index ad9c8c1de7..4947ddc560 100644 --- a/voxygen/src/ecs/sys/floater.rs +++ b/voxygen/src/ecs/sys/floater.rs @@ -3,7 +3,7 @@ use crate::ecs::{ ExpFloater, MyEntity, MyExpFloaterList, }; use common::{ - comp::{HealthSource, Pos, Stats}, + comp::{Health, HealthSource, Pos, Stats}, state::DeltaTime, sync::Uid, }; @@ -25,19 +25,30 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Uid>, ReadStorage<'a, Pos>, ReadStorage<'a, Stats>, + ReadStorage<'a, Health>, 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, + ( + entities, + my_entity, + dt, + mut my_exp_floater_list, + uids, + pos, + stats, + healths, + mut hp_floater_lists, + ): Self::SystemData, ) { - // Add hp floater lists to all entities with stats and a position + // Add hp floater lists to all entities with health and a position // Note: necessary in order to know last_hp - for (entity, last_hp) in (&entities, &stats, &pos, !&hp_floater_lists) + for (entity, last_hp) in (&entities, &healths, &pos, !&hp_floater_lists) .join() - .map(|(e, s, _, _)| (e, s.health.current())) + .map(|(e, h, _, _)| (e, h.current())) .collect::>() { let _ = hp_floater_lists.insert(entity, HpFloaterList { @@ -49,9 +60,7 @@ impl<'a> System<'a> for Sys { // 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)) + for (entity, health, hp_floater_list) in (&entities, &healths, &mut hp_floater_lists).join() { // Increment timer for time since last damaged by me hp_floater_list @@ -64,8 +73,8 @@ impl<'a> System<'a> for Sys { 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 + // Also, If we make health store a vec of the last_changes (from say the last + // frame), what if the client receives the health 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) @@ -101,8 +110,8 @@ impl<'a> System<'a> for Sys { } } - // Remove floater lists on entities without stats or without position - for entity in (&entities, !&stats, &hp_floater_lists) + // Remove floater lists on entities without health or without position + for entity in (&entities, !&healths, &hp_floater_lists) .join() .map(|(e, _, _)| e) .collect::>() diff --git a/voxygen/src/hud/group.rs b/voxygen/src/hud/group.rs index 1bfeaa1cb2..1d4d35d2c0 100644 --- a/voxygen/src/hud/group.rs +++ b/voxygen/src/hud/group.rs @@ -322,6 +322,7 @@ impl<'a> Widget for Group<'a> { let client_state = self.client.state(); let stats = client_state.ecs().read_storage::(); + let healths = client_state.ecs().read_storage::(); let energy = client_state.ecs().read_storage::(); let buffs = client_state.ecs().read_storage::(); let uid_allocator = client_state @@ -338,80 +339,84 @@ impl<'a> Widget for Group<'a> { self.show.group = true; let entity = uid_allocator.retrieve_entity_internal(uid.into()); let stats = entity.and_then(|entity| stats.get(entity)); + let health = entity.and_then(|entity| healths.get(entity)); let energy = entity.and_then(|entity| energy.get(entity)); let buffs = entity.and_then(|entity| buffs.get(entity)); + let is_leader = uid == leader; + if let Some(stats) = stats { let char_name = stats.name.to_string(); - let health_perc = stats.health.current() as f64 / stats.health.maximum() as f64; - - // change panel positions when debug info is shown - let back = if i == 0 { - Image::new(self.imgs.member_bg) - .top_left_with_margins_on(ui.window, offset, 20.0) - } else { - Image::new(self.imgs.member_bg) - .down_from(state.ids.member_panels_bg[i - 1], 45.0) - }; - let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer - let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); - let health_col = match (health_perc * 100.0) as u8 { - 0..=20 => crit_hp_color, - 21..=40 => LOW_HP_COLOR, - _ => HP_COLOR, - }; - let is_leader = uid == leader; - // Don't show panel for the player! - // Panel BG - back.w_h(152.0, 36.0) - .color(if is_leader { - Some(ERROR_COLOR) + if let Some(health) = health { + let health_perc = health.current() as f64 / health.maximum() as f64; + // change panel positions when debug info is shown + let back = if i == 0 { + Image::new(self.imgs.member_bg) + .top_left_with_margins_on(ui.window, offset, 20.0) } else { - Some(TEXT_COLOR) - }) - .set(state.ids.member_panels_bg[i], ui); - // Health - Image::new(self.imgs.bar_content) - .w_h(148.0 * health_perc, 22.0) - .color(Some(health_col)) - .top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0) - .set(state.ids.member_health[i], ui); - if stats.is_dead { - // Death Text - Text::new(&self.localized_strings.get("hud.group.dead")) - .mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0) - .font_size(20) - .font_id(self.fonts.cyri.conrod_id) - .color(KILL_COLOR) - .set(state.ids.dead_txt[i], ui); - } else { - // Health Text - let txt = format!( - "{}/{}", - stats.health.current() / 10 as u32, - stats.health.maximum() / 10 as u32, - ); - // Change font size depending on health amount - let font_size = match stats.health.maximum() { - 0..=999 => 14, - 1000..=9999 => 13, - 10000..=99999 => 12, - _ => 11, + Image::new(self.imgs.member_bg) + .down_from(state.ids.member_panels_bg[i - 1], 45.0) }; - // Change text offset depending on health amount - let txt_offset = match stats.health.maximum() { - 0..=999 => 4.0, - 1000..=9999 => 4.5, - 10000..=99999 => 5.0, - _ => 5.5, + let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 0.8; //Animation timer + let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); + let health_col = match (health_perc * 100.0) as u8 { + 0..=20 => crit_hp_color, + 21..=40 => LOW_HP_COLOR, + _ => HP_COLOR, }; - Text::new(&txt) - .mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset) - .font_size(font_size) - .font_id(self.fonts.cyri.conrod_id) - .color(Color::Rgba(1.0, 1.0, 1.0, 0.5)) - .set(state.ids.health_txt[i], ui); - }; + // Don't show panel for the player! + // Panel BG + back.w_h(152.0, 36.0) + .color(if is_leader { + Some(ERROR_COLOR) + } else { + Some(TEXT_COLOR) + }) + .set(state.ids.member_panels_bg[i], ui); + // Health + Image::new(self.imgs.bar_content) + .w_h(148.0 * health_perc, 22.0) + .color(Some(health_col)) + .top_left_with_margins_on(state.ids.member_panels_bg[i], 2.0, 2.0) + .set(state.ids.member_health[i], ui); + if health.is_dead { + // Death Text + Text::new(&self.localized_strings.get("hud.group.dead")) + .mid_top_with_margin_on(state.ids.member_panels_bg[i], 1.0) + .font_size(20) + .font_id(self.fonts.cyri.conrod_id) + .color(KILL_COLOR) + .set(state.ids.dead_txt[i], ui); + } else { + // Health Text + let txt = format!( + "{}/{}", + health.current() / 10 as u32, + health.maximum() / 10 as u32, + ); + // Change font size depending on health amount + let font_size = match health.maximum() { + 0..=999 => 14, + 1000..=9999 => 13, + 10000..=99999 => 12, + _ => 11, + }; + // Change text offset depending on health amount + let txt_offset = match health.maximum() { + 0..=999 => 4.0, + 1000..=9999 => 4.5, + 10000..=99999 => 5.0, + _ => 5.5, + }; + Text::new(&txt) + .mid_top_with_margin_on(state.ids.member_panels_bg[i], txt_offset) + .font_size(font_size) + .font_id(self.fonts.cyri.conrod_id) + .color(Color::Rgba(1.0, 1.0, 1.0, 0.5)) + .set(state.ids.health_txt[i], ui); + }; + } + // Panel Frame Image::new(self.imgs.member_frame) .w_h(152.0, 36.0) diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 1022a935dd..66a67c4bf6 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -753,6 +753,7 @@ impl Hud { let ecs = client.state().ecs(); let pos = ecs.read_storage::(); let stats = ecs.read_storage::(); + let healths = ecs.read_storage::(); let buffs = ecs.read_storage::(); let energy = ecs.read_storage::(); let hp_floater_lists = ecs.read_storage::(); @@ -767,11 +768,10 @@ impl Hud { .get(client.entity()) .map_or(0, |stats| stats.level.level()); //self.input = client.read_storage::(); - if let Some(stats) = stats.get(me) { + if let Some(health) = healths.get(me) { // Hurt Frame - let hp_percentage = - stats.health.current() as f32 / stats.health.maximum() as f32 * 100.0; - if hp_percentage < 10.0 && !stats.is_dead { + let hp_percentage = health.current() as f32 / health.maximum() as f32 * 100.0; + if hp_percentage < 10.0 && !health.is_dead { let hurt_fade = (self.pulse * (10.0 - hp_percentage as f32) * 0.1/* speed factor */).sin() * 0.5 @@ -792,7 +792,7 @@ impl Hud { .set(self.ids.alpha_text, ui_widgets); // Death Frame - if stats.is_dead { + if health.is_dead { Image::new(self.imgs.death_bg) .wh_of(ui_widgets.window) .middle_of(ui_widgets.window) @@ -801,7 +801,7 @@ impl Hud { .set(self.ids.death_bg, ui_widgets); } // Crosshair - let show_crosshair = (info.is_aiming || info.is_first_person) && !stats.is_dead; + let show_crosshair = (info.is_aiming || info.is_first_person) && !health.is_dead; self.crosshair_opacity = Lerp::lerp( self.crosshair_opacity, if show_crosshair { 1.0 } else { 0.0 }, @@ -850,11 +850,11 @@ impl Hud { // Render Player SCT numbers let mut player_sct_bg_id_walker = self.ids.player_sct_bgs.walk(); let mut player_sct_id_walker = self.ids.player_scts.walk(); - if let (Some(HpFloaterList { floaters, .. }), Some(stats)) = ( + if let (Some(HpFloaterList { floaters, .. }), Some(health)) = ( hp_floater_lists .get(me) .filter(|fl| !fl.floaters.is_empty()), - stats.get(me), + healths.get(me), ) { if global_state.settings.gameplay.sct_player_batch { let number_speed = 100.0; // Player Batched Numbers Speed @@ -871,7 +871,7 @@ impl Hud { let hp_damage = floaters.iter().fold(0, |acc, f| f.hp_change.min(0) + acc); // Divide by 10 to stay in the same dimension as the HP display let hp_dmg_rounded_abs = ((hp_damage + 5) / 10).abs(); - let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32; + let max_hp_frac = hp_damage.abs() as f32 / health.maximum() as f32; let timer = floaters .last() .expect("There must be at least one floater") @@ -927,8 +927,7 @@ impl Hud { &mut self.ids.player_scts, &mut ui_widgets.widget_id_generator(), ); - let max_hp_frac = - floater.hp_change.abs() as f32 / stats.health.maximum() as f32; + let max_hp_frac = floater.hp_change.abs() as f32 / health.maximum() as f32; // Increase font size based on fraction of maximum health // "flashes" by having a larger size in the first 100ms let font_size = 30 @@ -1152,11 +1151,12 @@ impl Hud { let speech_bubbles = &self.speech_bubbles; // Render overhead name tags and health bars - for (pos, info, bubble, stats, _, height_offset, hpfl, in_group) in ( + for (pos, info, bubble, _, health, _, height_offset, hpfl, in_group) in ( &entities, &pos, interpolated.maybe(), &stats, + &healths, &buffs, energy.maybe(), scales.maybe(), @@ -1166,12 +1166,24 @@ impl Hud { ) .join() .filter(|t| { - let stats = t.3; + let health = t.4; let entity = t.0; - entity != me && !stats.is_dead + entity != me && !health.is_dead }) .filter_map( - |(entity, pos, interpolated, stats, buffs, energy, scale, body, hpfl, uid)| { + |( + entity, + pos, + interpolated, + stats, + health, + buffs, + energy, + scale, + body, + hpfl, + uid, + )| { // Use interpolated position if available let pos = interpolated.map_or(pos.0, |i| i.pos); let in_group = client.group_members().contains_key(uid); @@ -1183,7 +1195,7 @@ impl Hud { let display_overhead_info = (info.target_entity.map_or(false, |e| e == entity) || info.selected_entity.map_or(false, |s| s.0 == entity) - || overhead::show_healthbar(stats) + || overhead::show_healthbar(health) || in_group) && dist_sqr < (if in_group { @@ -1201,6 +1213,7 @@ impl Hud { let info = display_overhead_info.then(|| overhead::Info { name: &stats.name, stats, + health, buffs, energy, }); @@ -1216,6 +1229,7 @@ impl Hud { info, bubble, stats, + health, buffs, body.height() * scale.map_or(1.0, |s| s.0) + 0.5, hpfl, @@ -1292,7 +1306,7 @@ impl Hud { }); // Divide by 10 to stay in the same dimension as the HP display let hp_dmg_rounded_abs = ((hp_damage + 5) / 10).abs(); - let max_hp_frac = hp_damage.abs() as f32 / stats.health.maximum() as f32; + let max_hp_frac = hp_damage.abs() as f32 / health.maximum() as f32; let timer = floaters .last() .expect("There must be at least one floater") @@ -1364,7 +1378,7 @@ impl Hud { .next(&mut self.ids.sct_bgs, &mut ui_widgets.widget_id_generator()); // Calculate total change let max_hp_frac = - floater.hp_change.abs() as f32 / stats.health.maximum() as f32; + floater.hp_change.abs() as f32 / health.maximum() as f32; // Increase font size based on fraction of maximum health // "flashes" by having a larger size in the first 100ms let font_size = 30 @@ -1897,6 +1911,7 @@ impl Hud { let ecs = client.state().ecs(); let entity = client.entity(); let stats = ecs.read_storage::(); + let healths = ecs.read_storage::(); let loadouts = ecs.read_storage::(); let energies = ecs.read_storage::(); let character_states = ecs.read_storage::(); @@ -1904,6 +1919,7 @@ impl Hud { let inventories = ecs.read_storage::(); if let ( Some(stats), + Some(health), Some(loadout), Some(energy), Some(_character_state), @@ -1911,6 +1927,7 @@ impl Hud { Some(inventory), ) = ( stats.get(entity), + healths.get(entity), loadouts.get(entity), energies.get(entity), character_states.get(entity), @@ -1924,6 +1941,7 @@ impl Hud { &self.fonts, &self.rot_imgs, &stats, + &health, &loadout, &energy, //&character_state, diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index 7ebfb29cfc..04ff0e2ab8 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -8,7 +8,7 @@ use crate::{ settings::GameplaySettings, ui::{fonts::ConrodVoxygenFonts, Ingameable}, }; -use common::comp::{BuffKind, Buffs, Energy, SpeechBubble, SpeechBubbleType, Stats}; +use common::comp::{BuffKind, Buffs, Energy, Health, SpeechBubble, SpeechBubbleType, Stats}; use conrod_core::{ color, position::Align, @@ -58,12 +58,13 @@ widget_ids! { pub struct Info<'a> { pub name: &'a str, pub stats: &'a Stats, + pub health: &'a Health, pub buffs: &'a Buffs, pub energy: Option<&'a Energy>, } /// Determines whether to show the healthbar -pub fn show_healthbar(stats: &Stats) -> bool { stats.health.current() != stats.health.maximum() } +pub fn show_healthbar(health: &Health) -> bool { health.current() != health.maximum() } /// ui widget containing everything that goes over a character's head /// (Speech bubble, Name, Level, HP/energy bars, etc.) @@ -141,7 +142,7 @@ impl<'a> Ingameable for Overhead<'a> { } else { 0 } - + if show_healthbar(info.stats) { + + if show_healthbar(info.health) { 5 + if info.energy.is_some() { 1 } else { 0 } } else { 0 @@ -171,17 +172,17 @@ impl<'a> Widget for Overhead<'a> { if let Some(Info { name, stats, + health, buffs, energy, }) = self.info { // Used to set healthbar colours based on hp_percentage - let hp_percentage = - stats.health.current() as f64 / stats.health.maximum() as f64 * 100.0; + let hp_percentage = health.current() as f64 / health.maximum() as f64 * 100.0; // Compare levels to decide if a skull is shown let level_comp = stats.level.level() as i64 - self.own_level as i64; - let health_current = (stats.health.current() / 10) as f64; - let health_max = (stats.health.maximum() / 10) as f64; + let health_current = (health.current() / 10) as f64; + let health_max = (health.maximum() / 10) as f64; let name_y = if (health_current - health_max).abs() < 1e-6 { MANA_BAR_Y + 20.0 } else if level_comp > 9 && !self.in_group { @@ -302,7 +303,7 @@ impl<'a> Widget for Overhead<'a> { .parent(id) .set(state.ids.name, ui); - if show_healthbar(stats) { + if show_healthbar(health) { // Show HP Bar let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); @@ -332,7 +333,7 @@ impl<'a> Widget for Overhead<'a> { .parent(id) .set(state.ids.health_bar, ui); let mut txt = format!("{}/{}", health_cur_txt, health_max_txt); - if stats.is_dead { + if health.is_dead { txt = self.voxygen_i18n.get("hud.group.dead").to_string() }; Text::new(&txt) diff --git a/voxygen/src/hud/skillbar.rs b/voxygen/src/hud/skillbar.rs index ad8045517d..0cebc454d3 100644 --- a/voxygen/src/hud/skillbar.rs +++ b/voxygen/src/hud/skillbar.rs @@ -20,7 +20,7 @@ use common::comp::{ tool::{Tool, ToolKind}, Hands, ItemKind, }, - Energy, Inventory, Loadout, Stats, + Energy, Health, Inventory, Loadout, Stats, }; use conrod_core::{ color, @@ -124,6 +124,7 @@ pub struct Skillbar<'a> { fonts: &'a ConrodVoxygenFonts, rot_imgs: &'a ImgsRot, stats: &'a Stats, + health: &'a Health, loadout: &'a Loadout, energy: &'a Energy, // character_state: &'a CharacterState, @@ -148,6 +149,7 @@ impl<'a> Skillbar<'a> { fonts: &'a ConrodVoxygenFonts, rot_imgs: &'a ImgsRot, stats: &'a Stats, + health: &'a Health, loadout: &'a Loadout, energy: &'a Energy, // character_state: &'a CharacterState, @@ -167,6 +169,7 @@ impl<'a> Skillbar<'a> { fonts, rot_imgs, stats, + health, loadout, energy, common: widget::CommonBuilder::default(), @@ -216,11 +219,10 @@ impl<'a> Widget for Skillbar<'a> { let exp_percentage = (self.stats.exp.current() as f64) / (self.stats.exp.maximum() as f64); - let mut hp_percentage = - self.stats.health.current() as f64 / self.stats.health.maximum() as f64 * 100.0; + let mut hp_percentage = self.health.current() as f64 / self.health.maximum() as f64 * 100.0; let mut energy_percentage = self.energy.current() as f64 / self.energy.maximum() as f64 * 100.0; - if self.stats.is_dead { + if self.health.is_dead { hp_percentage = 0.0; energy_percentage = 0.0; }; @@ -293,7 +295,7 @@ impl<'a> Widget for Skillbar<'a> { .set(state.ids.level_down, ui); } // Death message - if self.stats.is_dead { + if self.health.is_dead { if let Some(key) = self .global_state .settings @@ -400,12 +402,12 @@ impl<'a> Widget for Skillbar<'a> { if let BarNumbers::Values = bar_values { let mut hp_txt = format!( "{}/{}", - (self.stats.health.current() / 10).max(1) as u32, /* Don't show 0 health for - * living players */ - (self.stats.health.maximum() / 10) as u32 + (self.health.current() / 10).max(1) as u32, /* Don't show 0 health for + * living players */ + (self.health.maximum() / 10) as u32 ); let mut energy_txt = format!("{}", energy_percentage as u32); - if self.stats.is_dead { + if self.health.is_dead { hp_txt = self.localized_strings.get("hud.group.dead").to_string(); energy_txt = self.localized_strings.get("hud.group.dead").to_string(); }; @@ -438,7 +440,7 @@ impl<'a> Widget for Skillbar<'a> { if let BarNumbers::Percent = bar_values { let mut hp_txt = format!("{}%", hp_percentage as u32); let mut energy_txt = format!("{}", energy_percentage as u32); - if self.stats.is_dead { + if self.health.is_dead { hp_txt = self.localized_strings.get("hud.group.dead").to_string(); energy_txt = self.localized_strings.get("hud.group.dead").to_string(); }; diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index 6d9ace7ea6..e265152427 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -26,8 +26,8 @@ use anim::{ use common::{ comp::{ item::{ItemKind, ToolKind}, - Body, CharacterState, Item, Last, LightAnimation, LightEmitter, Loadout, Ori, PhysicsState, - Pos, Scale, Stats, Vel, + Body, CharacterState, Health, Item, Last, LightAnimation, LightEmitter, Loadout, Ori, + PhysicsState, Pos, Scale, Vel, }, span, state::{DeltaTime, State}, @@ -545,7 +545,7 @@ impl FigureMgr { character, last_character, physics, - stats, + health, loadout, item, ), @@ -559,7 +559,7 @@ impl FigureMgr { ecs.read_storage::().maybe(), ecs.read_storage::>().maybe(), &ecs.read_storage::(), - ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ) @@ -662,11 +662,11 @@ impl FigureMgr { }; // Change in health as color! - let col = stats - .map(|s| { + let col = health + .map(|h| { vek::Rgba::broadcast(1.0) + vek::Rgba::new(2.0, 2.0, 2., 0.00).map(|c| { - (c / (1.0 + DAMAGE_FADE_COEFFICIENT * s.health.last_change.0)) as f32 + (c / (1.0 + DAMAGE_FADE_COEFFICIENT * h.last_change.0)) as f32 }) }) .unwrap_or(vek::Rgba::broadcast(1.0)) @@ -2749,13 +2749,13 @@ impl FigureMgr { &ecs.read_storage::(), ecs.read_storage::().maybe(), &ecs.read_storage::(), - ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ) .join() // Don't render dead entities - .filter(|(_, _, _, _, stats, _, _)| stats.map_or(true, |s| !s.is_dead)) + .filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) .for_each(|(entity, pos, _, body, _, loadout, _)| { if let Some((locals, bone_consts, model, _)) = self.get_model_for_render( tick, @@ -2803,13 +2803,13 @@ impl FigureMgr { &ecs.read_storage::(), ecs.read_storage::().maybe(), &ecs.read_storage::(), - ecs.read_storage::().maybe(), + ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ) .join() // Don't render dead entities - .filter(|(_, _, _, _, stats, _, _)| stats.map_or(true, |s| !s.is_dead)) + .filter(|(_, _, _, _, health, _, _)| health.map_or(true, |h| !h.is_dead)) { let is_player = entity == player_entity; @@ -2853,10 +2853,9 @@ impl FigureMgr { ecs.read_storage::().get(player_entity), ecs.read_storage::().get(player_entity), ) { - let stats_storage = state.read_storage::(); - let stats = stats_storage.get(player_entity); - - if stats.map_or(false, |s| s.is_dead) { + let healths = state.read_storage::(); + let health = healths.get(player_entity); + if health.map_or(false, |h| h.is_dead) { return; } diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 5e61543cb7..4cd499c7f8 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -604,10 +604,10 @@ impl Scene { .maybe(), scene_data.state.ecs().read_storage::().maybe(), &scene_data.state.ecs().read_storage::(), - &scene_data.state.ecs().read_storage::(), + &scene_data.state.ecs().read_storage::(), ) .join() - .filter(|(_, _, _, _, stats)| !stats.is_dead) + .filter(|(_, _, _, _, health)| !health.is_dead) .filter(|(pos, _, _, _, _)| { (pos.0.distance_squared(player_pos) as f32) < (loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powf(2.0) From 87bff41a6674e4a5153158349ee8317fa279c604 Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 1 Nov 2020 11:15:46 -0600 Subject: [PATCH 5/9] Addressed comments. --- common/src/comp/health.rs | 4 +-- common/src/explosion.rs | 3 ++- server/src/cmd.rs | 17 +++++------- server/src/events/entity_manipulation.rs | 21 +++++++++------ server/src/state_ext.rs | 4 +-- server/src/sys/object.rs | 33 ++++++++---------------- voxygen/src/hud/mod.rs | 2 +- voxygen/src/hud/overhead.rs | 6 ++--- 8 files changed, 40 insertions(+), 50 deletions(-) diff --git a/common/src/comp/health.rs b/common/src/comp/health.rs index 7dd3ff3385..a9309be24b 100644 --- a/common/src/comp/health.rs +++ b/common/src/comp/health.rs @@ -120,10 +120,10 @@ impl Component for Health { } #[derive(Copy, Clone, Debug, Serialize, Deserialize)] -pub struct Dying { +pub struct Dead { pub cause: HealthSource, } -impl Component for Dying { +impl Component for Dead { type Storage = IdvStorage; } diff --git a/common/src/explosion.rs b/common/src/explosion.rs index 941d534c59..c0a96caab1 100644 --- a/common/src/explosion.rs +++ b/common/src/explosion.rs @@ -1,4 +1,4 @@ -use crate::combat::Damages; +use crate::{combat::Damages, effect::Effect}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -12,4 +12,5 @@ pub struct Explosion { pub enum RadiusEffect { Damages(Damages), TerrainDestruction(f32), + EntityEffect(Effect), } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 683915b11c..0c3c317fee 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -10,6 +10,7 @@ use chrono::{NaiveTime, Timelike}; use common::{ cmd::{ChatCommand, CHAT_COMMANDS, CHAT_SHORTCUTS}, comp::{self, ChatType, Item, LightEmitter, WaypointArea}, + effect::Effect, event::{EventBus, ServerEvent}, msg::{DisconnectReason, Notification, PlayerListUpdate, ServerGeneral}, npc::{self, get_npc_name}, @@ -18,7 +19,7 @@ use common::{ terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, util::Dir, vol::RectVolSize, - Damage, DamageSource, Damages, Explosion, LoadoutBuilder, RadiusEffect, + Explosion, LoadoutBuilder, RadiusEffect, }; use rand::Rng; use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; @@ -1157,16 +1158,10 @@ fn handle_explosion( pos: pos.0, explosion: Explosion { effects: vec![ - RadiusEffect::Damages(Damages::new( - Some(Damage { - source: DamageSource::Explosion, - value: 100.0 * power, - }), - Some(Damage { - source: DamageSource::Explosion, - value: 100.0 * power, - }), - )), + RadiusEffect::EntityEffect(Effect::Health(comp::HealthChange { + amount: (-100.0 * power) as i32, + cause: comp::HealthSource::Explosion { owner: None }, + })), RadiusEffect::TerrainDestruction(power), ], radius: 3.0 * power, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index e38107577f..348d6d7a9c 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -516,15 +516,10 @@ pub fn handle_explosion( // Add an outcome // Uses radius as outcome power, makes negative if explosion has healing effect - #[allow(clippy::blocks_in_if_conditions)] let outcome_power = explosion.radius - * if explosion.effects.iter().any(|e| { - if let RadiusEffect::Damages(d) = e { - d.contains_damage(DamageSource::Healing) - } else { - false - } - }) { + * if explosion.effects.iter().any( + |e| matches!(e, RadiusEffect::Damages(d) if d.contains_damage(DamageSource::Healing)), + ) { -1.0 } else { 1.0 @@ -672,6 +667,16 @@ pub fn handle_explosion( .cast(); } }, + RadiusEffect::EntityEffect(effect) => { + for (entity, pos_entity) in + (&ecs.entities(), &ecs.read_storage::()).join() + { + let distance_squared = pos.distance_squared(pos_entity.0); + if distance_squared < explosion.radius.powi(2) { + server.state().apply_effect(entity, effect); + } + } + }, } } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index cd806f88f6..3ffa32252d 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -20,7 +20,7 @@ use vek::*; pub trait StateExt { /// Updates a component associated with the entity based on the `Effect` - fn apply_effect(&mut self, entity: EcsEntity, effect: Effect); + fn apply_effect(&self, entity: EcsEntity, effect: Effect); /// Build a non-player character fn create_npc( &mut self, @@ -71,7 +71,7 @@ pub trait StateExt { } impl StateExt for State { - fn apply_effect(&mut self, entity: EcsEntity, effect: Effect) { + fn apply_effect(&self, entity: EcsEntity, effect: Effect) { match effect { Effect::Health(change) => { self.ecs() diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index 110895e009..7b86eb73b5 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -1,9 +1,10 @@ use common::{ - comp::{HealthSource, Object, PhysicsState, Pos, Vel}, + comp::{HealthChange, HealthSource, Object, PhysicsState, Pos, Vel}, + effect::Effect, event::{EventBus, ServerEvent}, span, state::DeltaTime, - Damage, DamageSource, Damages, Explosion, RadiusEffect, + Explosion, RadiusEffect, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; @@ -49,16 +50,10 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: Explosion { effects: vec![ - RadiusEffect::Damages(Damages::new( - Some(Damage { - source: DamageSource::Explosion, - value: 500.0, - }), - Some(Damage { - source: DamageSource::Explosion, - value: 500.0, - }), - )), + RadiusEffect::EntityEffect(Effect::Health(HealthChange { + amount: -500, + cause: HealthSource::Explosion { owner: *owner }, + })), RadiusEffect::TerrainDestruction(4.0), ], radius: 12.0, @@ -79,16 +74,10 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: Explosion { effects: vec![ - RadiusEffect::Damages(Damages::new( - Some(Damage { - source: DamageSource::Explosion, - value: 50.0, - }), - Some(Damage { - source: DamageSource::Explosion, - value: 50.0, - }), - )), + RadiusEffect::EntityEffect(Effect::Health(HealthChange { + amount: -50, + cause: HealthSource::Explosion { owner: *owner }, + })), RadiusEffect::TerrainDestruction(4.0), ], radius: 12.0, diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 66a67c4bf6..38f26f98f0 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -1195,7 +1195,7 @@ impl Hud { let display_overhead_info = (info.target_entity.map_or(false, |e| e == entity) || info.selected_entity.map_or(false, |s| s.0 == entity) - || overhead::show_healthbar(health) + || overhead::should_show_healthbar(health) || in_group) && dist_sqr < (if in_group { diff --git a/voxygen/src/hud/overhead.rs b/voxygen/src/hud/overhead.rs index 04ff0e2ab8..6d2630b3ad 100644 --- a/voxygen/src/hud/overhead.rs +++ b/voxygen/src/hud/overhead.rs @@ -64,7 +64,7 @@ pub struct Info<'a> { } /// Determines whether to show the healthbar -pub fn show_healthbar(health: &Health) -> bool { health.current() != health.maximum() } +pub fn should_show_healthbar(health: &Health) -> bool { health.current() != health.maximum() } /// ui widget containing everything that goes over a character's head /// (Speech bubble, Name, Level, HP/energy bars, etc.) @@ -142,7 +142,7 @@ impl<'a> Ingameable for Overhead<'a> { } else { 0 } - + if show_healthbar(info.health) { + + if should_show_healthbar(info.health) { 5 + if info.energy.is_some() { 1 } else { 0 } } else { 0 @@ -303,7 +303,7 @@ impl<'a> Widget for Overhead<'a> { .parent(id) .set(state.ids.name, ui); - if show_healthbar(health) { + if should_show_healthbar(health) { // Show HP Bar let hp_ani = (self.pulse * 4.0/* speed factor */).cos() * 0.5 + 1.0; //Animation timer let crit_hp_color: Color = Color::Rgba(0.79, 0.19, 0.17, hp_ani); From d38f1d319c871f541d35bb934bdb1291c975de9e Mon Sep 17 00:00:00 2001 From: Sam Date: Sun, 1 Nov 2020 18:26:01 -0600 Subject: [PATCH 6/9] Energy and health change server events now reference EcsEntity instead of Uid. Added TargetGroup to use to determine targets of effects/damage. Added Entity(TargetGroup, Effect) to RadiusEffect enum. --- common/src/combat.rs | 13 ++++- common/src/event.rs | 4 +- common/src/explosion.rs | 7 ++- common/src/lib.rs | 2 +- common/src/sys/beam.rs | 66 ++++++++++++------------ common/src/sys/buff.rs | 9 ++-- common/src/sys/melee.rs | 17 +++--- common/src/sys/projectile.rs | 47 ++++++++++++----- common/src/sys/shockwave.rs | 14 +++-- server/src/cmd.rs | 11 ++-- server/src/events/entity_manipulation.rs | 54 ++++++++++++------- server/src/events/mod.rs | 6 +-- server/src/state_ext.rs | 4 ++ server/src/sys/object.rs | 22 +++++--- 14 files changed, 172 insertions(+), 104 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index ed11e2fb5e..c89419fcd5 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -21,8 +21,11 @@ pub struct Damages { impl Damages { pub fn new(enemy: Option, group: Option) -> Self { Damages { enemy, group } } - pub fn get_damage(self, same_group: bool) -> Option { - if same_group { self.group } else { self.enemy } + pub fn get_damage(self, group_target: GroupTarget) -> Option { + match group_target { + GroupTarget::InGroup => self.group, + GroupTarget::OutOfGroup => self.enemy, + } } pub fn contains_damage(self, source: DamageSource) -> bool { @@ -31,6 +34,12 @@ impl Damages { } } +#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +pub enum GroupTarget { + InGroup, + OutOfGroup, +} + #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum DamageSource { Melee, diff --git a/common/src/event.rs b/common/src/event.rs index 41965191da..4275aeb519 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -34,7 +34,7 @@ pub enum ServerEvent { reagent: Option, }, Damage { - uid: Uid, + entity: EcsEntity, change: comp::HealthChange, }, Destroy { @@ -111,7 +111,7 @@ pub enum ServerEvent { buff_change: comp::BuffChange, }, EnergyChange { - uid: Uid, + entity: EcsEntity, change: comp::EnergyChange, }, } diff --git a/common/src/explosion.rs b/common/src/explosion.rs index c0a96caab1..8de12f367c 100644 --- a/common/src/explosion.rs +++ b/common/src/explosion.rs @@ -1,4 +1,7 @@ -use crate::{combat::Damages, effect::Effect}; +use crate::{ + combat::{Damages, GroupTarget}, + effect::Effect, +}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -12,5 +15,5 @@ pub struct Explosion { pub enum RadiusEffect { Damages(Damages), TerrainDestruction(f32), - EntityEffect(Effect), + Entity(Option, Effect), } diff --git a/common/src/lib.rs b/common/src/lib.rs index 1959d85135..f7e166c7fa 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -53,6 +53,6 @@ pub mod util; pub mod vol; pub mod volumes; -pub use combat::{Damage, DamageSource, Damages, Knockback}; +pub use combat::{Damage, DamageSource, Damages, GroupTarget, Knockback}; pub use explosion::{Explosion, RadiusEffect}; pub use loadout_builder::LoadoutBuilder; diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index 2f81ce7bc3..d6cb680d90 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -6,7 +6,7 @@ use crate::{ event::{EventBus, ServerEvent}, state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, - DamageSource, + DamageSource, GroupTarget, }; use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; @@ -68,8 +68,8 @@ impl<'a> System<'a> for Sys { let dt = dt.0; // Beams - for (entity, uid, pos, ori, beam_segment) in - (&entities, &uids, &positions, &orientations, &beam_segments).join() + for (entity, pos, ori, beam_segment) in + (&entities, &positions, &orientations, &beam_segments).join() { let creation_time = match beam_segment.creation { Some(time) => time, @@ -163,12 +163,19 @@ impl<'a> System<'a> for Sys { .map(|group_a| Some(group_a) == groups.get(b)) .unwrap_or(Some(*uid_b) == beam_segment.owner); + let target_group = if same_group { + GroupTarget::InGroup + } else { + GroupTarget::OutOfGroup + }; + // If owner, shouldn't heal or damage if Some(*uid_b) == beam_segment.owner { continue; } - let damage = if let Some(damage) = beam_segment.damages.get_damage(same_group) { + let damage = if let Some(damage) = beam_segment.damages.get_damage(target_group) + { damage } else { continue; @@ -181,25 +188,22 @@ impl<'a> System<'a> for Sys { let change = damage.modify_damage(block, loadouts.get(b), beam_segment.owner); if !matches!(damage.source, DamageSource::Healing) { - server_emitter.emit(ServerEvent::Damage { - uid: *uid_b, - change, - }); - if beam_segment.lifesteal_eff > 0.0 { - server_emitter.emit(ServerEvent::Damage { - uid: beam_segment.owner.unwrap_or(*uid), - change: HealthChange { - amount: (-change.amount as f32 * beam_segment.lifesteal_eff) - as i32, - cause: HealthSource::Healing { - by: beam_segment.owner, + server_emitter.emit(ServerEvent::Damage { entity: b, change }); + if let Some(entity) = beam_owner { + if beam_segment.lifesteal_eff > 0.0 { + server_emitter.emit(ServerEvent::Damage { + entity, + change: HealthChange { + amount: (-change.amount as f32 * beam_segment.lifesteal_eff) + as i32, + cause: HealthSource::Healing { + by: beam_segment.owner, + }, }, - }, - }); - } - if let Some(uid) = beam_segment.owner { + }); + } server_emitter.emit(ServerEvent::EnergyChange { - uid, + entity, change: EnergyChange { amount: beam_segment.energy_regen as i32, source: EnergySource::HitEnemy, @@ -208,19 +212,15 @@ impl<'a> System<'a> for Sys { } } else if let Some(energy) = beam_owner.and_then(|o| energies.get(o)) { if energy.current() > beam_segment.energy_cost { - if let Some(uid) = beam_segment.owner { - server_emitter.emit(ServerEvent::EnergyChange { - uid, - change: EnergyChange { - amount: -(beam_segment.energy_cost as i32), // Stamina use - source: EnergySource::Ability, - }, - }) - } - server_emitter.emit(ServerEvent::Damage { - uid: *uid_b, - change, + server_emitter.emit(ServerEvent::EnergyChange { + entity: beam_owner.unwrap(), /* If it's able to get an energy + * component, the entity exists */ + change: EnergyChange { + amount: -(beam_segment.energy_cost as i32), // Stamina use + source: EnergySource::Ability, + }, }); + server_emitter.emit(ServerEvent::Damage { entity: b, change }); } } // Adds entities that were hit to the hit_entities list on the beam, sees if it diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 271f19d49a..9cb76930b3 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -5,7 +5,6 @@ use crate::{ }, event::{EventBus, ServerEvent}, state::DeltaTime, - sync::Uid, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; @@ -17,7 +16,6 @@ impl<'a> System<'a> for Sys { Entities<'a>, Read<'a, DeltaTime>, Read<'a, EventBus>, - ReadStorage<'a, Uid>, ReadStorage<'a, Loadout>, WriteStorage<'a, Health>, WriteStorage<'a, Buffs>, @@ -25,14 +23,13 @@ impl<'a> System<'a> for Sys { fn run( &mut self, - (entities, dt, server_bus, uids, loadouts, mut healths, mut buffs): Self::SystemData, + (entities, dt, server_bus, loadouts, mut healths, mut buffs): Self::SystemData, ) { let mut server_emitter = server_bus.emitter(); // Set to false to avoid spamming server buffs.set_event_emission(false); healths.set_event_emission(false); - for (entity, buff_comp, uid, health) in (&entities, &mut buffs, &uids, &mut healths).join() - { + for (entity, buff_comp, health) in (&entities, &mut buffs, &mut healths).join() { let mut expired_buffs = Vec::::new(); for (id, buff) in buff_comp.buffs.iter_mut() { // Tick the buff and subtract delta from it @@ -94,7 +91,7 @@ impl<'a> System<'a> for Sys { HealthSource::Buff { owner: buff_owner } }; server_emitter.emit(ServerEvent::Damage { - uid: *uid, + entity, change: HealthChange { amount: *accumulated as i32, cause, diff --git a/common/src/sys/melee.rs b/common/src/sys/melee.rs index 93be75b795..ca77866ba2 100644 --- a/common/src/sys/melee.rs +++ b/common/src/sys/melee.rs @@ -5,6 +5,7 @@ use crate::{ span, sync::Uid, util::Dir, + GroupTarget, }; use rand::{thread_rng, Rng}; use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage}; @@ -75,9 +76,8 @@ impl<'a> System<'a> for Sys { attack.applied = true; // Go through all other entities - for (b, uid_b, pos_b, ori_b, scale_b_maybe, character_b, health_b, body_b) in ( + for (b, pos_b, ori_b, scale_b_maybe, character_b, health_b, body_b) in ( &entities, - &uids, &positions, &orientations, scales.maybe(), @@ -110,7 +110,13 @@ impl<'a> System<'a> for Sys { .map(|group_a| Some(group_a) == groups.get(b)) .unwrap_or(false); - let damage = if let Some(damage) = attack.damages.get_damage(same_group) { + let target_group = if same_group { + GroupTarget::InGroup + } else { + GroupTarget::OutOfGroup + }; + + let damage = if let Some(damage) = attack.damages.get_damage(target_group) { damage } else { continue; @@ -122,10 +128,7 @@ impl<'a> System<'a> for Sys { let change = damage.modify_damage(block, loadouts.get(b), Some(*uid)); if change.amount != 0 { - server_emitter.emit(ServerEvent::Damage { - uid: *uid_b, - change, - }); + server_emitter.emit(ServerEvent::Damage { entity: b, change }); // Apply bleeding buff on melee hits with 10% chance // TODO: Don't have buff uniformly applied on all melee attacks diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 5959a6ce2f..2650b443b8 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -9,6 +9,7 @@ use crate::{ span, state::DeltaTime, sync::UidAllocator, + GroupTarget, }; use rand::{thread_rng, Rng}; use specs::{ @@ -83,6 +84,13 @@ impl<'a> System<'a> for Sys { .retrieve_entity_internal(other.into()) .and_then(|e| groups.get(e)) ); + + let target_group = if same_group { + GroupTarget::InGroup + } else { + GroupTarget::OutOfGroup + }; + if projectile.ignore_group // Skip if in the same group && same_group @@ -100,36 +108,49 @@ impl<'a> System<'a> for Sys { if Some(other) == projectile.owner { continue; } - let damage = if let Some(damage) = damages.get_damage(same_group) { + let damage = if let Some(damage) = damages.get_damage(target_group) { damage } else { continue; }; - let other_entity_loadout = uid_allocator - .retrieve_entity_internal(other.into()) - .and_then(|e| loadouts.get(e)); - let change = - damage.modify_damage(false, other_entity_loadout, projectile.owner); + if let Some(other_entity) = + uid_allocator.retrieve_entity_internal(other.into()) + { + let other_entity_loadout = loadouts.get(other_entity); + let change = damage.modify_damage( + false, + other_entity_loadout, + projectile.owner, + ); - if change.amount != 0 { - server_emitter.emit(ServerEvent::Damage { uid: other, change }); + if change.amount != 0 { + server_emitter.emit(ServerEvent::Damage { + entity: other_entity, + change, + }); + } } }, projectile::Effect::Knockback(knockback) => { - if let Some(entity) = + if let Some(other_entity) = uid_allocator.retrieve_entity_internal(other.into()) { let impulse = knockback.calculate_impulse(ori.0); if !impulse.is_approx_zero() { - local_emitter - .emit(LocalEvent::ApplyImpulse { entity, impulse }); + local_emitter.emit(LocalEvent::ApplyImpulse { + entity: other_entity, + impulse, + }); } } }, projectile::Effect::RewardEnergy(energy) => { - if let Some(uid) = projectile.owner { + if let Some(entity_owner) = projectile + .owner + .and_then(|u| uid_allocator.retrieve_entity_internal(u.into())) + { server_emitter.emit(ServerEvent::EnergyChange { - uid, + entity: entity_owner, change: EnergyChange { amount: energy as i32, source: EnergySource::HitEnemy, diff --git a/common/src/sys/shockwave.rs b/common/src/sys/shockwave.rs index f85fe7cce6..b71f900fda 100644 --- a/common/src/sys/shockwave.rs +++ b/common/src/sys/shockwave.rs @@ -7,6 +7,7 @@ use crate::{ state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, util::Dir, + GroupTarget, }; use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; use vek::*; @@ -177,6 +178,12 @@ impl<'a> System<'a> for Sys { .map(|group_a| Some(group_a) == groups.get(b)) .unwrap_or(Some(*uid_b) == shockwave.owner); + let target_group = if same_group { + GroupTarget::InGroup + } else { + GroupTarget::OutOfGroup + }; + // Check if it is a hit let hit = entity != b && !health_b.is_dead @@ -192,7 +199,7 @@ impl<'a> System<'a> for Sys { && (!shockwave.requires_ground || physics_state_b.on_ground); if hit { - let damage = if let Some(damage) = shockwave.damages.get_damage(same_group) { + let damage = if let Some(damage) = shockwave.damages.get_damage(target_group) { damage } else { continue; @@ -205,10 +212,7 @@ impl<'a> System<'a> for Sys { let change = damage.modify_damage(block, loadouts.get(b), Some(owner_uid)); if change.amount != 0 { - server_emitter.emit(ServerEvent::Damage { - uid: *uid_b, - change, - }); + server_emitter.emit(ServerEvent::Damage { entity: b, change }); shockwave_hit_list.hit_entities.push(*uid_b); let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); let impulse = shockwave.knockback.calculate_impulse(kb_dir); diff --git a/server/src/cmd.rs b/server/src/cmd.rs index 0c3c317fee..daffafd1df 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -1158,10 +1158,13 @@ fn handle_explosion( pos: pos.0, explosion: Explosion { effects: vec![ - RadiusEffect::EntityEffect(Effect::Health(comp::HealthChange { - amount: (-100.0 * power) as i32, - cause: comp::HealthSource::Explosion { owner: None }, - })), + RadiusEffect::Entity( + None, + Effect::Health(comp::HealthChange { + amount: (-100.0 * power) as i32, + cause: comp::HealthSource::Explosion { owner: None }, + }), + ), RadiusEffect::TerrainDestruction(power), ], radius: 3.0 * power, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 348d6d7a9c..5d613001ab 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -19,7 +19,7 @@ use common::{ sys::melee::BLOCK_ANGLE, terrain::{Block, TerrainGrid}, vol::ReadVol, - Damage, DamageSource, Explosion, RadiusEffect, + Damage, DamageSource, Explosion, GroupTarget, RadiusEffect, }; use comp::item::Reagent; use rand::prelude::*; @@ -27,12 +27,10 @@ use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt use tracing::error; use vek::Vec3; -pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) { +pub fn handle_damage(server: &Server, entity: EcsEntity, change: HealthChange) { let ecs = &server.state.ecs(); - if let Some(entity) = ecs.entity_from_uid(uid.into()) { - if let Some(health) = ecs.write_storage::().get_mut(entity) { - health.change_by(change); - } + if let Some(health) = ecs.write_storage::().get_mut(entity) { + health.change_by(change); } } @@ -570,7 +568,13 @@ pub fn handle_explosion( } } - let mut damage = if let Some(damage) = damages.get_damage(same_group) { + let target_group = if same_group { + GroupTarget::InGroup + } else { + GroupTarget::OutOfGroup + }; + + let mut damage = if let Some(damage) = damages.get_damage(target_group) { damage } else { continue; @@ -667,13 +671,29 @@ pub fn handle_explosion( .cast(); } }, - RadiusEffect::EntityEffect(effect) => { - for (entity, pos_entity) in - (&ecs.entities(), &ecs.read_storage::()).join() + RadiusEffect::Entity(target_group, effect) => { + for (entity_b, pos_b) in (&ecs.entities(), &ecs.read_storage::()).join() { - let distance_squared = pos.distance_squared(pos_entity.0); - if distance_squared < explosion.radius.powi(2) { - server.state().apply_effect(entity, effect); + let distance_squared = pos.distance_squared(pos_b.0); + // See if entities are in the same group + let mut same_group = owner_entity + .and_then(|e| groups.get(e)) + .map_or(false, |group_a| Some(group_a) == groups.get(entity_b)); + if let Some(entity) = owner_entity { + if entity == entity_b { + same_group = true; + } + } + let hit_group = if same_group { + GroupTarget::InGroup + } else { + GroupTarget::OutOfGroup + }; + + if distance_squared < explosion.radius.powi(2) + && target_group.map_or(true, |g| g == hit_group) + { + server.state().apply_effect(entity_b, effect); } } }, @@ -754,11 +774,9 @@ pub fn handle_buff(server: &mut Server, entity: EcsEntity, buff_change: buff::Bu } } -pub fn handle_energy_change(server: &Server, uid: Uid, change: EnergyChange) { +pub fn handle_energy_change(server: &Server, entity: EcsEntity, change: EnergyChange) { let ecs = &server.state.ecs(); - if let Some(entity) = ecs.entity_from_uid(uid.into()) { - if let Some(energy) = ecs.write_storage::().get_mut(entity) { - energy.change_by(change); - } + if let Some(energy) = ecs.write_storage::().get_mut(entity) { + energy.change_by(change); } } diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 7c083d8be0..edcc319842 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -82,7 +82,7 @@ impl Server { ServerEvent::Knockback { entity, impulse } => { handle_knockback(&self, entity, impulse) }, - ServerEvent::Damage { uid, change } => handle_damage(&self, uid, change), + ServerEvent::Damage { entity, change } => handle_damage(&self, entity, change), ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause), ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip), ServerEvent::GroupManip(entity, manip) => handle_group(self, entity, manip), @@ -137,8 +137,8 @@ impl Server { entity, buff_change, } => handle_buff(self, entity, buff_change), - ServerEvent::EnergyChange { uid, change } => { - handle_energy_change(&self, uid, change) + ServerEvent::EnergyChange { entity, change } => { + handle_energy_change(&self, entity, change) }, } } diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 3ffa32252d..253a417542 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -242,6 +242,10 @@ impl StateExt for State { z_max: body.height(), }); self.write_component(entity, body); + self.write_component( + entity, + comp::Health::new(stats.body_type, stats.level.level()), + ); self.write_component(entity, stats); self.write_component(entity, inventory); self.write_component(entity, loadout); diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index 7b86eb73b5..519e7d5cad 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -50,10 +50,13 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: Explosion { effects: vec![ - RadiusEffect::EntityEffect(Effect::Health(HealthChange { - amount: -500, - cause: HealthSource::Explosion { owner: *owner }, - })), + RadiusEffect::Entity( + None, + Effect::Health(HealthChange { + amount: -500, + cause: HealthSource::Explosion { owner: *owner }, + }), + ), RadiusEffect::TerrainDestruction(4.0), ], radius: 12.0, @@ -74,10 +77,13 @@ impl<'a> System<'a> for Sys { pos: pos.0, explosion: Explosion { effects: vec![ - RadiusEffect::EntityEffect(Effect::Health(HealthChange { - amount: -50, - cause: HealthSource::Explosion { owner: *owner }, - })), + RadiusEffect::Entity( + None, + Effect::Health(HealthChange { + amount: -50, + cause: HealthSource::Explosion { owner: *owner }, + }), + ), RadiusEffect::TerrainDestruction(4.0), ], radius: 12.0, From bda7fefdc05f3fc85babd8983a2957cbfeda6c91 Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 3 Nov 2020 20:01:12 -0600 Subject: [PATCH 7/9] Removed Damages struct. Added GroupTarget enum. Changed RadiusEffect to use Effect instead of Daamges. Added Damage variant to Effect Eenum. --- common/src/combat.rs | 47 +-------- common/src/comp/beam.rs | 4 +- common/src/comp/character_state.rs | 6 +- common/src/comp/inventory/item/tool.rs | 93 +++++++++-------- common/src/comp/projectile.rs | 4 +- common/src/comp/shockwave.rs | 4 +- common/src/effect.rs | 20 +++- common/src/explosion.rs | 6 +- common/src/lib.rs | 2 +- common/src/states/basic_beam.rs | 7 +- common/src/states/basic_melee.rs | 13 +-- common/src/states/charged_melee.rs | 4 +- common/src/states/charged_ranged.rs | 4 +- common/src/states/combo_melee.rs | 13 +-- common/src/states/dash_melee.rs | 4 +- common/src/states/leap_melee.rs | 13 +-- common/src/states/shockwave.rs | 13 +-- common/src/states/spin_melee.rs | 13 +-- common/src/sys/beam.rs | 125 +++++++++++------------ common/src/sys/melee.rs | 42 +++----- common/src/sys/projectile.rs | 33 +++--- common/src/sys/shockwave.rs | 32 ++---- server/src/events/entity_manipulation.rs | 110 ++++++-------------- server/src/events/inventory_manip.rs | 2 +- server/src/state_ext.rs | 14 ++- 25 files changed, 258 insertions(+), 370 deletions(-) diff --git a/common/src/combat.rs b/common/src/combat.rs index c89419fcd5..143761f500 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -6,34 +6,6 @@ use crate::{ use serde::{Deserialize, Serialize}; use vek::*; -pub const BLOCK_EFFICIENCY: f32 = 0.9; - -/// Each section of this struct determines what damage is applied to a -/// particular target, using some identifier -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] -pub struct Damages { - /// Targets enemies, and all other creatures not in your group - pub enemy: Option, - /// Targets people in the same group as you, and any pets you have - pub group: Option, -} - -impl Damages { - pub fn new(enemy: Option, group: Option) -> Self { Damages { enemy, group } } - - pub fn get_damage(self, group_target: GroupTarget) -> Option { - match group_target { - GroupTarget::InGroup => self.group, - GroupTarget::OutOfGroup => self.enemy, - } - } - - pub fn contains_damage(self, source: DamageSource) -> bool { - self.enemy.map_or(false, |e| e.source == source) - || self.group.map_or(false, |g| g.source == source) - } -} - #[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] pub enum GroupTarget { InGroup, @@ -58,12 +30,7 @@ pub struct Damage { } impl Damage { - pub fn modify_damage( - self, - block: bool, - loadout: Option<&Loadout>, - uid: Option, - ) -> HealthChange { + pub fn modify_damage(self, loadout: Option<&Loadout>, uid: Option) -> HealthChange { let mut damage = self.value; match self.source { DamageSource::Melee => { @@ -72,10 +39,6 @@ impl Damage { if rand::random() { critdamage = damage * 0.3; } - // Block - if block { - damage *= 1.0 - BLOCK_EFFICIENCY - } // Armor let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); damage *= 1.0 - damage_reduction; @@ -95,10 +58,6 @@ impl Damage { if rand::random() { damage *= 1.2; } - // Block - if block { - damage *= 1.0 - BLOCK_EFFICIENCY - } // Armor let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); damage *= 1.0 - damage_reduction; @@ -109,10 +68,6 @@ impl Damage { } }, DamageSource::Explosion => { - // Block - if block { - damage *= 1.0 - BLOCK_EFFICIENCY - } // Armor let damage_reduction = loadout.map_or(0.0, |l| l.get_damage_reduction()); damage *= 1.0 - damage_reduction; diff --git a/common/src/comp/beam.rs b/common/src/comp/beam.rs index 2c15b7fede..302b4d8437 100644 --- a/common/src/comp/beam.rs +++ b/common/src/comp/beam.rs @@ -1,4 +1,4 @@ -use crate::{sync::Uid, Damages}; +use crate::{sync::Uid, Damage, GroupTarget}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -8,7 +8,7 @@ use std::time::Duration; pub struct Properties { pub angle: f32, pub speed: f32, - pub damages: Damages, + pub damages: Vec<(Option, Damage)>, pub lifesteal_eff: f32, pub energy_regen: u32, pub energy_cost: u32, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index 4b05a7df63..f1b7e91502 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -3,7 +3,7 @@ use crate::{ event::{LocalEvent, ServerEvent}, states::*, sys::character_behavior::JoinData, - Damages, Knockback, + Damage, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage, VecStorage}; @@ -155,9 +155,9 @@ impl Component for CharacterState { type Storage = FlaggedStorage>; } -#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Attacking { - pub damages: Damages, + pub damages: Vec<(Option, Damage)>, pub range: f32, pub max_angle: f32, pub applied: bool, diff --git a/common/src/comp/inventory/item/tool.rs b/common/src/comp/inventory/item/tool.rs index 9dac188ada..aa015b2d9a 100644 --- a/common/src/comp/inventory/item/tool.rs +++ b/common/src/comp/inventory/item/tool.rs @@ -7,8 +7,9 @@ use crate::{ buff::{Buff, BuffCategory, BuffData, BuffKind, BuffSource}, projectile, Body, CharacterAbility, Gravity, LightEmitter, Projectile, }, + effect::Effect, states::combo_melee, - Damage, DamageSource, Damages, Explosion, Knockback, RadiusEffect, + Damage, DamageSource, Explosion, GroupTarget, Knockback, RadiusEffect, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -302,13 +303,10 @@ impl Tool { projectile: Projectile { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ - projectile::Effect::Damages(Damages::new( - Some(Damage { - source: DamageSource::Projectile, - value: 40.0 * self.base_power(), - }), - None, - )), + projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), Damage { + source: DamageSource::Projectile, + value: 40.0 * self.base_power(), + }), projectile::Effect::Knockback(Knockback::Away(10.0)), projectile::Effect::RewardEnergy(50), projectile::Effect::Vanish, @@ -360,13 +358,10 @@ impl Tool { projectile: Projectile { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ - projectile::Effect::Damages(Damages::new( - Some(Damage { - source: DamageSource::Projectile, - value: 40.0 * self.base_power(), - }), - None, - )), + projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), Damage { + source: DamageSource::Projectile, + value: 40.0 * self.base_power(), + }), projectile::Effect::Knockback(Knockback::Away(10.0)), projectile::Effect::Vanish, projectile::Effect::Buff { @@ -425,16 +420,22 @@ impl Tool { projectile: Projectile { hit_solid: vec![ projectile::Effect::Explode(Explosion { - effects: vec![RadiusEffect::Damages(Damages::new( - Some(Damage { - source: DamageSource::Explosion, - value: 50.0 * self.base_power(), - }), - Some(Damage { - source: DamageSource::Healing, - value: 140.0 * self.base_power(), - }), - ))], + effects: vec![ + RadiusEffect::Entity( + Some(GroupTarget::OutOfGroup), + Effect::Damage(Damage { + source: DamageSource::Explosion, + value: 50.0 * self.base_power(), + }), + ), + RadiusEffect::Entity( + Some(GroupTarget::InGroup), + Effect::Damage(Damage { + source: DamageSource::Healing, + value: 140.0 * self.base_power(), + }), + ), + ], radius: 3.0 + 2.5 * self.base_power(), energy_regen: 0, }), @@ -442,16 +443,22 @@ impl Tool { ], hit_entity: vec![ projectile::Effect::Explode(Explosion { - effects: vec![RadiusEffect::Damages(Damages::new( - Some(Damage { - source: DamageSource::Explosion, - value: 50.0 * self.base_power(), - }), - Some(Damage { - source: DamageSource::Healing, - value: 140.0 * self.base_power(), - }), - ))], + effects: vec![ + RadiusEffect::Entity( + Some(GroupTarget::OutOfGroup), + Effect::Damage(Damage { + source: DamageSource::Explosion, + value: 50.0 * self.base_power(), + }), + ), + RadiusEffect::Entity( + Some(GroupTarget::InGroup), + Effect::Damage(Damage { + source: DamageSource::Healing, + value: 140.0 * self.base_power(), + }), + ), + ], radius: 3.0 + 2.5 * self.base_power(), energy_regen: 0, }), @@ -478,13 +485,13 @@ impl Tool { projectile: Projectile { hit_solid: vec![ projectile::Effect::Explode(Explosion { - effects: vec![RadiusEffect::Damages(Damages::new( - Some(Damage { + effects: vec![RadiusEffect::Entity( + Some(GroupTarget::OutOfGroup), + Effect::Damage(Damage { source: DamageSource::Explosion, value: 100.0 * self.base_power(), }), - None, - ))], + )], radius: 5.0, energy_regen: 50, }), @@ -492,13 +499,13 @@ impl Tool { ], hit_entity: vec![ projectile::Effect::Explode(Explosion { - effects: vec![RadiusEffect::Damages(Damages::new( - Some(Damage { + effects: vec![RadiusEffect::Entity( + Some(GroupTarget::OutOfGroup), + Effect::Damage(Damage { source: DamageSource::Explosion, value: 100.0 * self.base_power(), }), - None, - ))], + )], radius: 5.0, energy_regen: 50, }), diff --git a/common/src/comp/projectile.rs b/common/src/comp/projectile.rs index e90bbc94f0..f24bd79b50 100644 --- a/common/src/comp/projectile.rs +++ b/common/src/comp/projectile.rs @@ -1,4 +1,4 @@ -use crate::{comp::Buff, sync::Uid, Damages, Explosion, Knockback}; +use crate::{comp::Buff, sync::Uid, Damage, Explosion, GroupTarget, Knockback}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -6,7 +6,7 @@ use std::time::Duration; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Effect { - Damages(Damages), + Damage(Option, Damage), Knockback(Knockback), RewardEnergy(u32), Explode(Explosion), diff --git a/common/src/comp/shockwave.rs b/common/src/comp/shockwave.rs index 2d5de87726..677ffc43ec 100644 --- a/common/src/comp/shockwave.rs +++ b/common/src/comp/shockwave.rs @@ -1,4 +1,4 @@ -use crate::{sync::Uid, Damages, Knockback}; +use crate::{sync::Uid, Damage, GroupTarget, Knockback}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -9,7 +9,7 @@ pub struct Properties { pub angle: f32, pub vertical_angle: f32, pub speed: f32, - pub damages: Damages, + pub damages: Vec<(Option, Damage)>, pub knockback: Knockback, pub requires_ground: bool, pub duration: Duration, diff --git a/common/src/effect.rs b/common/src/effect.rs index 2fb644e7f6..369fafb5ca 100644 --- a/common/src/effect.rs +++ b/common/src/effect.rs @@ -1,11 +1,12 @@ -use crate::comp; +use crate::{combat, comp}; use serde::{Deserialize, Serialize}; /// An effect that may be applied to an entity -#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum Effect { Health(comp::HealthChange), Xp(i64), + Damage(combat::Damage), } impl Effect { @@ -13,6 +14,21 @@ impl Effect { match self { Effect::Health(c) => format!("{:+} health", c.amount), Effect::Xp(n) => format!("{:+} exp", n), + Effect::Damage(d) => format!("{:+}", d.value), + } + } + + pub fn modify_strength(&mut self, modifier: f32) { + match self { + Effect::Health(change) => { + change.amount = (change.amount as f32 * modifier) as i32; + }, + Effect::Xp(amount) => { + *amount = (*amount as f32 * modifier) as i64; + }, + Effect::Damage(damage) => { + damage.interpolate_damage(modifier, 0.0); + }, } } } diff --git a/common/src/explosion.rs b/common/src/explosion.rs index 8de12f367c..889602ecd2 100644 --- a/common/src/explosion.rs +++ b/common/src/explosion.rs @@ -1,7 +1,4 @@ -use crate::{ - combat::{Damages, GroupTarget}, - effect::Effect, -}; +use crate::{combat::GroupTarget, effect::Effect}; use serde::{Deserialize, Serialize}; #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -13,7 +10,6 @@ pub struct Explosion { #[derive(Clone, Debug, PartialEq, Serialize, Deserialize)] pub enum RadiusEffect { - Damages(Damages), TerrainDestruction(f32), Entity(Option, Effect), } diff --git a/common/src/lib.rs b/common/src/lib.rs index f7e166c7fa..25ea1b3669 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -53,6 +53,6 @@ pub mod util; pub mod vol; pub mod volumes; -pub use combat::{Damage, DamageSource, Damages, GroupTarget, Knockback}; +pub use combat::{Damage, DamageSource, GroupTarget, Knockback}; pub use explosion::{Explosion, RadiusEffect}; pub use loadout_builder::LoadoutBuilder; diff --git a/common/src/states/basic_beam.rs b/common/src/states/basic_beam.rs index 579777b980..bb254d704b 100644 --- a/common/src/states/basic_beam.rs +++ b/common/src/states/basic_beam.rs @@ -6,7 +6,7 @@ use crate::{ states::utils::*, sync::Uid, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, DamageSource, Damages, + Damage, DamageSource, GroupTarget, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -126,7 +126,10 @@ impl CharacterBehavior for Data { let properties = beam::Properties { angle: self.static_data.max_angle.to_radians(), speed, - damages: Damages::new(Some(damage), Some(heal)), + damages: vec![ + (Some(GroupTarget::OutOfGroup), damage), + (Some(GroupTarget::InGroup), heal), + ], lifesteal_eff: self.static_data.lifesteal_eff, energy_regen, energy_cost, diff --git a/common/src/states/basic_melee.rs b/common/src/states/basic_melee.rs index e6f0b05a11..888aac20da 100644 --- a/common/src/states/basic_melee.rs +++ b/common/src/states/basic_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -76,13 +76,10 @@ impl CharacterBehavior for Data { // Hit attempt data.updater.insert(data.entity, Attacking { - damages: Damages::new( - Some(Damage { - source: DamageSource::Melee, - value: self.static_data.base_damage as f32, - }), - None, - ), + damages: vec![(Some(GroupTarget::OutOfGroup), Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32, + })], range: self.static_data.range, max_angle: 180_f32.to_radians(), applied: false, diff --git a/common/src/states/charged_melee.rs b/common/src/states/charged_melee.rs index 9c6216bc84..9e795ca379 100644 --- a/common/src/states/charged_melee.rs +++ b/common/src/states/charged_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::{StageSection, *}, sys::character_behavior::*, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -125,7 +125,7 @@ impl CharacterBehavior for Data { // Hit attempt data.updater.insert(data.entity, Attacking { - damages: Damages::new(Some(damage), None), + damages: vec![(Some(GroupTarget::OutOfGroup), damage)], range: self.static_data.range, max_angle: self.static_data.max_angle.to_radians(), applied: false, diff --git a/common/src/states/charged_ranged.rs b/common/src/states/charged_ranged.rs index bbd29dee68..983ffe2199 100644 --- a/common/src/states/charged_ranged.rs +++ b/common/src/states/charged_ranged.rs @@ -7,7 +7,7 @@ use crate::{ event::ServerEvent, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -99,7 +99,7 @@ impl CharacterBehavior for Data { let mut projectile = Projectile { hit_solid: vec![projectile::Effect::Stick], hit_entity: vec![ - projectile::Effect::Damages(Damages::new(Some(damage), None)), + projectile::Effect::Damage(Some(GroupTarget::OutOfGroup), damage), projectile::Effect::Knockback(Knockback::Away(knockback)), projectile::Effect::Vanish, projectile::Effect::Buff { diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index df2c6f885d..4c926d07c9 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -131,13 +131,10 @@ impl CharacterBehavior for Data { * self.static_data.stage_data[stage_index].damage_increase, ); data.updater.insert(data.entity, Attacking { - damages: Damages::new( - Some(Damage { - source: DamageSource::Melee, - value: damage as f32, - }), - None, - ), + damages: vec![(Some(GroupTarget::OutOfGroup), Damage { + source: DamageSource::Melee, + value: damage as f32, + })], range: self.static_data.stage_data[stage_index].range, max_angle: self.static_data.stage_data[stage_index].angle.to_radians(), applied: false, diff --git a/common/src/states/dash_melee.rs b/common/src/states/dash_melee.rs index 8e141c2e68..391f469c2b 100644 --- a/common/src/states/dash_melee.rs +++ b/common/src/states/dash_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, EnergyChange, EnergySource, StateUpdate}, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -139,7 +139,7 @@ impl CharacterBehavior for Data { * charge_frac + self.static_data.base_knockback; data.updater.insert(data.entity, Attacking { - damages: Damages::new(Some(damage), None), + damages: vec![(Some(GroupTarget::OutOfGroup), damage)], range: self.static_data.range, max_angle: self.static_data.angle.to_radians(), applied: false, diff --git a/common/src/states/leap_melee.rs b/common/src/states/leap_melee.rs index 886375c76e..db12c0ebc8 100644 --- a/common/src/states/leap_melee.rs +++ b/common/src/states/leap_melee.rs @@ -2,7 +2,7 @@ use crate::{ comp::{Attacking, CharacterState, StateUpdate}, states::utils::{StageSection, *}, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -133,13 +133,10 @@ impl CharacterBehavior for Data { if !self.exhausted { // Hit attempt, when animation plays data.updater.insert(data.entity, Attacking { - damages: Damages::new( - Some(Damage { - source: DamageSource::Melee, - value: self.static_data.base_damage as f32, - }), - None, - ), + damages: vec![(Some(GroupTarget::OutOfGroup), Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32, + })], range: self.static_data.range, max_angle: self.static_data.max_angle.to_radians(), applied: false, diff --git a/common/src/states/shockwave.rs b/common/src/states/shockwave.rs index 0141f0112a..cb3dc7fd3a 100644 --- a/common/src/states/shockwave.rs +++ b/common/src/states/shockwave.rs @@ -3,7 +3,7 @@ use crate::{ event::ServerEvent, states::utils::*, sys::character_behavior::{CharacterBehavior, JoinData}, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -70,13 +70,10 @@ impl CharacterBehavior for Data { vertical_angle: self.static_data.shockwave_vertical_angle, speed: self.static_data.shockwave_speed, duration: self.static_data.shockwave_duration, - damages: Damages::new( - Some(Damage { - source: DamageSource::Shockwave, - value: self.static_data.damage as f32, - }), - None, - ), + damages: vec![(Some(GroupTarget::OutOfGroup), Damage { + source: DamageSource::Shockwave, + value: self.static_data.damage as f32, + })], knockback: self.static_data.knockback, requires_ground: self.static_data.requires_ground, owner: Some(*data.uid), diff --git a/common/src/states/spin_melee.rs b/common/src/states/spin_melee.rs index 452bded7b0..17c458b289 100644 --- a/common/src/states/spin_melee.rs +++ b/common/src/states/spin_melee.rs @@ -5,7 +5,7 @@ use crate::{ character_behavior::{CharacterBehavior, JoinData}, phys::GRAVITY, }, - Damage, DamageSource, Damages, Knockback, + Damage, DamageSource, GroupTarget, Knockback, }; use serde::{Deserialize, Serialize}; use std::time::Duration; @@ -108,13 +108,10 @@ impl CharacterBehavior for Data { }); // Hit attempt data.updater.insert(data.entity, Attacking { - damages: Damages::new( - Some(Damage { - source: DamageSource::Melee, - value: self.static_data.base_damage as f32, - }), - None, - ), + damages: vec![(Some(GroupTarget::OutOfGroup), Damage { + source: DamageSource::Melee, + value: self.static_data.base_damage as f32, + })], range: self.static_data.range, max_angle: 180_f32.to_radians(), applied: false, diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index d6cb680d90..11156c74d6 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -1,19 +1,17 @@ use crate::{ comp::{ - group, Beam, BeamSegment, Body, CharacterState, Energy, EnergyChange, EnergySource, Health, - HealthChange, HealthSource, Last, Loadout, Ori, Pos, Scale, + group, Beam, BeamSegment, Body, Energy, EnergyChange, EnergySource, Health, HealthChange, + HealthSource, Last, Loadout, Ori, Pos, Scale, }, event::{EventBus, ServerEvent}, state::{DeltaTime, Time}, sync::{Uid, UidAllocator}, - DamageSource, GroupTarget, + GroupTarget, }; use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; use vek::*; -pub const BLOCK_ANGLE: f32 = 180.0; - /// This system is responsible for handling beams that heal or do damage pub struct Sys; impl<'a> System<'a> for Sys { @@ -33,7 +31,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Health>, ReadStorage<'a, Loadout>, ReadStorage<'a, group::Group>, - ReadStorage<'a, CharacterState>, ReadStorage<'a, Energy>, WriteStorage<'a, BeamSegment>, WriteStorage<'a, Beam>, @@ -56,7 +53,6 @@ impl<'a> System<'a> for Sys { healths, loadouts, groups, - character_states, energies, mut beam_segments, mut beams, @@ -116,25 +112,13 @@ impl<'a> System<'a> for Sys { }; // Go through all other effectable entities - for ( - b, - uid_b, - pos_b, - last_pos_b_maybe, - ori_b, - scale_b_maybe, - character_b, - health_b, - body_b, - ) in ( + for (b, uid_b, pos_b, last_pos_b_maybe, scale_b_maybe, health_b, body_b) in ( &entities, &uids, &positions, // TODO: make sure that these are maintained on the client and remove `.maybe()` last_positions.maybe(), - &orientations, scales.maybe(), - character_states.maybe(), &healths, &bodies, ) @@ -174,58 +158,63 @@ impl<'a> System<'a> for Sys { continue; } - let damage = if let Some(damage) = beam_segment.damages.get_damage(target_group) - { - damage - } else { - continue; - }; - - let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) - // TODO: investigate whether this calculation is proper for beams - && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; - - let change = damage.modify_damage(block, loadouts.get(b), beam_segment.owner); - - if !matches!(damage.source, DamageSource::Healing) { - server_emitter.emit(ServerEvent::Damage { entity: b, change }); - if let Some(entity) = beam_owner { - if beam_segment.lifesteal_eff > 0.0 { - server_emitter.emit(ServerEvent::Damage { - entity, - change: HealthChange { - amount: (-change.amount as f32 * beam_segment.lifesteal_eff) - as i32, - cause: HealthSource::Healing { - by: beam_segment.owner, - }, - }, - }); + for (target, damage) in beam_segment.damages.iter() { + if let Some(target) = target { + if *target != target_group { + continue; } - server_emitter.emit(ServerEvent::EnergyChange { - entity, - change: EnergyChange { - amount: beam_segment.energy_regen as i32, - source: EnergySource::HitEnemy, - }, - }); } - } else if let Some(energy) = beam_owner.and_then(|o| energies.get(o)) { - if energy.current() > beam_segment.energy_cost { - server_emitter.emit(ServerEvent::EnergyChange { - entity: beam_owner.unwrap(), /* If it's able to get an energy - * component, the entity exists */ - change: EnergyChange { - amount: -(beam_segment.energy_cost as i32), // Stamina use - source: EnergySource::Ability, - }, - }); - server_emitter.emit(ServerEvent::Damage { entity: b, change }); + + // Modify damage + let change = damage.modify_damage(loadouts.get(b), beam_segment.owner); + + match target { + Some(GroupTarget::OutOfGroup) => { + server_emitter.emit(ServerEvent::Damage { entity: b, change }); + if let Some(entity) = beam_owner { + server_emitter.emit(ServerEvent::Damage { + entity, + change: HealthChange { + amount: (-change.amount as f32 + * beam_segment.lifesteal_eff) + as i32, + cause: HealthSource::Healing { + by: beam_segment.owner, + }, + }, + }); + server_emitter.emit(ServerEvent::EnergyChange { + entity, + change: EnergyChange { + amount: beam_segment.energy_regen as i32, + source: EnergySource::HitEnemy, + }, + }); + } + }, + Some(GroupTarget::InGroup) => { + if let Some(energy) = beam_owner.and_then(|o| energies.get(o)) { + if energy.current() > beam_segment.energy_cost { + server_emitter.emit(ServerEvent::EnergyChange { + entity: beam_owner.unwrap(), /* If it's able to get an energy + * component, the entity exists */ + change: EnergyChange { + amount: -(beam_segment.energy_cost as i32), // Stamina use + source: EnergySource::Ability, + }, + }); + server_emitter + .emit(ServerEvent::Damage { entity: b, change }); + } + } + }, + None => {}, } + + // Adds entities that were hit to the hit_entities list on the beam, sees if + // it needs to purge the hit_entities list + hit_entities.push(*uid_b); } - // Adds entities that were hit to the hit_entities list on the beam, sees if it - // needs to purge the hit_entities list - hit_entities.push(*uid_b); } } } diff --git a/common/src/sys/melee.rs b/common/src/sys/melee.rs index ca77866ba2..c22371a9d4 100644 --- a/common/src/sys/melee.rs +++ b/common/src/sys/melee.rs @@ -1,5 +1,5 @@ use crate::{ - comp::{buff, group, Attacking, Body, CharacterState, Health, Loadout, Ori, Pos, Scale}, + comp::{buff, group, Attacking, Body, Health, Loadout, Ori, Pos, Scale}, event::{EventBus, LocalEvent, ServerEvent}, metrics::SysMetrics, span, @@ -12,8 +12,6 @@ use specs::{Entities, Join, Read, ReadExpect, ReadStorage, System, WriteStorage} use std::time::Duration; use vek::*; -pub const BLOCK_ANGLE: f32 = 180.0; - /// This system is responsible for handling accepted inputs like moving or /// attacking pub struct Sys; @@ -32,7 +30,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Health>, ReadStorage<'a, Loadout>, ReadStorage<'a, group::Group>, - ReadStorage<'a, CharacterState>, WriteStorage<'a, Attacking>, ); @@ -51,7 +48,6 @@ impl<'a> System<'a> for Sys { healths, loadouts, groups, - character_states, mut attacking_storage, ): Self::SystemData, ) { @@ -76,16 +72,8 @@ impl<'a> System<'a> for Sys { attack.applied = true; // Go through all other entities - for (b, pos_b, ori_b, scale_b_maybe, character_b, health_b, body_b) in ( - &entities, - &positions, - &orientations, - scales.maybe(), - character_states.maybe(), - &healths, - &bodies, - ) - .join() + for (b, pos_b, scale_b_maybe, health_b, body_b) in + (&entities, &positions, scales.maybe(), &healths, &bodies).join() { // 2D versions let pos2 = Vec2::from(pos.0); @@ -116,20 +104,16 @@ impl<'a> System<'a> for Sys { GroupTarget::OutOfGroup }; - let damage = if let Some(damage) = attack.damages.get_damage(target_group) { - damage - } else { - continue; - }; + for (target, damage) in attack.damages.iter() { + if let Some(target) = target { + if *target != target_group { + continue; + } + } - let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) - && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; + let change = damage.modify_damage(loadouts.get(b), Some(*uid)); - let change = damage.modify_damage(block, loadouts.get(b), Some(*uid)); - - if change.amount != 0 { server_emitter.emit(ServerEvent::Damage { entity: b, change }); - // Apply bleeding buff on melee hits with 10% chance // TODO: Don't have buff uniformly applied on all melee attacks if change.amount < 0 && thread_rng().gen::() < 0.1 { @@ -147,15 +131,13 @@ impl<'a> System<'a> for Sys { )), }); } - attack.hit_count += 1; - } - - if change.amount != 0 { let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); let impulse = attack.knockback.calculate_impulse(kb_dir); if !impulse.is_approx_zero() { server_emitter.emit(ServerEvent::Knockback { entity: b, impulse }); } + + attack.hit_count += 1; } } } diff --git a/common/src/sys/projectile.rs b/common/src/sys/projectile.rs index 2650b443b8..0e4a8b2a07 100644 --- a/common/src/sys/projectile.rs +++ b/common/src/sys/projectile.rs @@ -104,31 +104,27 @@ impl<'a> System<'a> for Sys { for effect in projectile.hit_entity.drain(..) { match effect { - projectile::Effect::Damages(damages) => { + projectile::Effect::Damage(target, damage) => { if Some(other) == projectile.owner { continue; } - let damage = if let Some(damage) = damages.get_damage(target_group) { - damage - } else { - continue; - }; + + if let Some(target) = target { + if target != target_group { + continue; + } + } + if let Some(other_entity) = uid_allocator.retrieve_entity_internal(other.into()) { let other_entity_loadout = loadouts.get(other_entity); - let change = damage.modify_damage( - false, - other_entity_loadout, - projectile.owner, - ); - - if change.amount != 0 { - server_emitter.emit(ServerEvent::Damage { - entity: other_entity, - change, - }); - } + let change = + damage.modify_damage(other_entity_loadout, projectile.owner); + server_emitter.emit(ServerEvent::Damage { + entity: other_entity, + change, + }); } }, projectile::Effect::Knockback(knockback) => { @@ -177,6 +173,7 @@ impl<'a> System<'a> for Sys { } } }, + // TODO: Change to effect after !1472 merges projectile::Effect::Buff { buff, chance } => { if let Some(entity) = uid_allocator.retrieve_entity_internal(other.into()) diff --git a/common/src/sys/shockwave.rs b/common/src/sys/shockwave.rs index b71f900fda..de13dfc17a 100644 --- a/common/src/sys/shockwave.rs +++ b/common/src/sys/shockwave.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - group, Body, CharacterState, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, - Scale, Shockwave, ShockwaveHitEntities, + group, Body, Health, HealthSource, Last, Loadout, Ori, PhysicsState, Pos, Scale, Shockwave, + ShockwaveHitEntities, }, event::{EventBus, LocalEvent, ServerEvent}, state::{DeltaTime, Time}, @@ -12,8 +12,6 @@ use crate::{ use specs::{saveload::MarkerAllocator, Entities, Join, Read, ReadStorage, System, WriteStorage}; use vek::*; -pub const BLOCK_ANGLE: f32 = 180.0; - /// This system is responsible for handling accepted inputs like moving or /// attacking pub struct Sys; @@ -35,7 +33,6 @@ impl<'a> System<'a> for Sys { ReadStorage<'a, Health>, ReadStorage<'a, Loadout>, ReadStorage<'a, group::Group>, - ReadStorage<'a, CharacterState>, ReadStorage<'a, PhysicsState>, WriteStorage<'a, Shockwave>, WriteStorage<'a, ShockwaveHitEntities>, @@ -59,7 +56,6 @@ impl<'a> System<'a> for Sys { healths, loadouts, groups, - character_states, physics_states, mut shockwaves, mut shockwave_hit_lists, @@ -131,9 +127,7 @@ impl<'a> System<'a> for Sys { uid_b, pos_b, last_pos_b_maybe, - ori_b, scale_b_maybe, - character_b, health_b, body_b, physics_state_b, @@ -143,9 +137,7 @@ impl<'a> System<'a> for Sys { &positions, // TODO: make sure that these are maintained on the client and remove `.maybe()` last_positions.maybe(), - &orientations, scales.maybe(), - character_states.maybe(), &healths, &bodies, &physics_states, @@ -199,21 +191,19 @@ impl<'a> System<'a> for Sys { && (!shockwave.requires_ground || physics_state_b.on_ground); if hit { - let damage = if let Some(damage) = shockwave.damages.get_damage(target_group) { - damage - } else { - continue; - }; + for (target, damage) in shockwave.damages.iter() { + if let Some(target) = target { + if *target != target_group { + continue; + } + } - let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) - && ori_b.0.angle_between(pos.0 - pos_b.0) < BLOCK_ANGLE.to_radians() / 2.0; + let owner_uid = shockwave.owner.unwrap_or(*uid); + let change = damage.modify_damage(loadouts.get(b), Some(owner_uid)); - let owner_uid = shockwave.owner.unwrap_or(*uid); - let change = damage.modify_damage(block, loadouts.get(b), Some(owner_uid)); - - if change.amount != 0 { server_emitter.emit(ServerEvent::Damage { entity: b, change }); shockwave_hit_list.hit_entities.push(*uid_b); + let kb_dir = Dir::new((pos_b.0 - pos.0).try_normalized().unwrap_or(*ori.0)); let impulse = shockwave.knockback.calculate_impulse(kb_dir); if !impulse.is_approx_zero() { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 5d613001ab..7fb60ec268 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -11,12 +11,12 @@ use common::{ object, Alignment, Body, Energy, EnergyChange, Group, Health, HealthChange, HealthSource, Item, Player, Pos, Stats, }, + effect::Effect, lottery::Lottery, msg::{PlayerListUpdate, ServerGeneral}, outcome::Outcome, state::BlockChange, sync::{Uid, UidAllocator, WorldSyncExt}, - sys::melee::BLOCK_ANGLE, terrain::{Block, TerrainGrid}, vol::ReadVol, Damage, DamageSource, Explosion, GroupTarget, RadiusEffect, @@ -457,7 +457,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3) value: falldmg, }; let loadouts = state.ecs().read_storage::(); - let change = damage.modify_damage(false, loadouts.get(entity), None); + let change = damage.modify_damage(loadouts.get(entity), None); health.change_by(change); } } @@ -516,7 +516,7 @@ pub fn handle_explosion( // Uses radius as outcome power, makes negative if explosion has healing effect let outcome_power = explosion.radius * if explosion.effects.iter().any( - |e| matches!(e, RadiusEffect::Damages(d) if d.contains_damage(DamageSource::Healing)), + |e| matches!(e, RadiusEffect::Entity(_, e) if matches!(e, Effect::Damage(d) if matches!(d.source, DamageSource::Healing))), ) { -1.0 } else { @@ -530,7 +530,7 @@ pub fn handle_explosion( is_attack: explosion .effects .iter() - .any(|e| matches!(e, RadiusEffect::Damages(_))), + .any(|e| matches!(e, RadiusEffect::Entity(_, e) if matches!(e, Effect::Damage(_)))), reagent, }); let owner_entity = owner.and_then(|uid| { @@ -541,70 +541,6 @@ pub fn handle_explosion( for effect in explosion.effects { match effect { - RadiusEffect::Damages(damages) => { - for (entity_b, pos_b, ori_b, character_b, health_b, loadout_b) in ( - &ecs.entities(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ecs.read_storage::().maybe(), - &mut ecs.write_storage::(), - ecs.read_storage::().maybe(), - ) - .join() - { - let distance_squared = pos.distance_squared(pos_b.0); - // Check if it is a hit - if !health_b.is_dead - // RADIUS - && distance_squared < explosion.radius.powi(2) - { - // See if entities are in the same group - let mut same_group = owner_entity - .and_then(|e| groups.get(e)) - .map_or(false, |group_a| Some(group_a) == groups.get(entity_b)); - if let Some(entity) = owner_entity { - if entity == entity_b { - same_group = true; - } - } - - let target_group = if same_group { - GroupTarget::InGroup - } else { - GroupTarget::OutOfGroup - }; - - let mut damage = if let Some(damage) = damages.get_damage(target_group) { - damage - } else { - continue; - }; - - let strength = 1.0 - distance_squared / explosion.radius.powi(2); - damage.interpolate_damage(strength, 0.0); - - let block = character_b.map(|c_b| c_b.is_block()).unwrap_or(false) - && ori_b.0.angle_between(pos - pos_b.0) - < BLOCK_ANGLE.to_radians() / 2.0; - - let change = damage.modify_damage(block, loadout_b, owner); - - if change.amount != 0 { - health_b.change_by(change); - if let Some(owner) = owner_entity { - if let Some(energy) = - ecs.write_storage::().get_mut(owner) - { - energy.change_by(EnergyChange { - amount: explosion.energy_regen as i32, - source: comp::EnergySource::HitEnemy, - }); - } - } - } - } - } - }, RadiusEffect::TerrainDestruction(power) => { const RAYS: usize = 500; @@ -671,10 +607,14 @@ pub fn handle_explosion( .cast(); } }, - RadiusEffect::Entity(target_group, effect) => { - for (entity_b, pos_b) in (&ecs.entities(), &ecs.read_storage::()).join() + RadiusEffect::Entity(target, mut effect) => { + for (entity_b, pos_b, health_b) in ( + &ecs.entities(), + &ecs.read_storage::(), + &ecs.read_storage::(), + ) + .join() { - let distance_squared = pos.distance_squared(pos_b.0); // See if entities are in the same group let mut same_group = owner_entity .and_then(|e| groups.get(e)) @@ -684,16 +624,34 @@ pub fn handle_explosion( same_group = true; } } - let hit_group = if same_group { + let target_group = if same_group { GroupTarget::InGroup } else { GroupTarget::OutOfGroup }; - if distance_squared < explosion.radius.powi(2) - && target_group.map_or(true, |g| g == hit_group) - { - server.state().apply_effect(entity_b, effect); + if let Some(target) = target { + if target != target_group { + continue; + } + } + + let distance_squared = pos.distance_squared(pos_b.0); + let strength = 1.0 - distance_squared / explosion.radius.powi(2); + + if strength > 0.0 && !health_b.is_dead { + effect.modify_strength(strength); + server.state().apply_effect(entity_b, effect, owner); + // Apply energy change + if let Some(owner) = owner_entity { + if let Some(energy) = ecs.write_storage::().get_mut(owner) + { + energy.change_by(EnergyChange { + amount: explosion.energy_regen as i32, + source: comp::EnergySource::HitEnemy, + }); + } + } } } }, diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index f6b517321a..bd20506e24 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -349,7 +349,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv drop(inventories); if let Some(effect) = maybe_effect { - state.apply_effect(entity, effect); + state.apply_effect(entity, effect, None); } if let Some(event) = event { state.write_component(entity, comp::InventoryUpdate::new(event)); diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 253a417542..e10144f8ee 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -20,7 +20,7 @@ use vek::*; pub trait StateExt { /// Updates a component associated with the entity based on the `Effect` - fn apply_effect(&self, entity: EcsEntity, effect: Effect); + fn apply_effect(&self, entity: EcsEntity, effect: Effect, source: Option); /// Build a non-player character fn create_npc( &mut self, @@ -71,7 +71,7 @@ pub trait StateExt { } impl StateExt for State { - fn apply_effect(&self, entity: EcsEntity, effect: Effect) { + fn apply_effect(&self, entity: EcsEntity, effect: Effect, source: Option) { match effect { Effect::Health(change) => { self.ecs() @@ -85,6 +85,16 @@ impl StateExt for State { .get_mut(entity) .map(|stats| stats.exp.change_by(xp)); }, + Effect::Damage(damage) => { + #[allow(irrefutable_let_patterns)] + if let loadout = self.ecs().read_storage::().get(entity) { + let change = damage.modify_damage(loadout, source); + self.ecs() + .write_storage::() + .get_mut(entity) + .map(|health| health.change_by(change)); + } + }, } } From a0af3159306e07d1a9c572d495d54e729b36a0c7 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 4 Nov 2020 19:21:42 -0600 Subject: [PATCH 8/9] Addressed comments. --- assets/voxygen/i18n/de_DE.ron | 5 +- assets/voxygen/i18n/en.ron | 5 +- assets/voxygen/i18n/tr_TR.ron | 5 +- client/src/lib.rs | 8 +- common/src/combat.rs | 37 +- common/src/comp/chat.rs | 2 +- common/src/comp/health.rs | 16 +- common/src/sys/agent.rs | 9 +- common/src/sys/beam.rs | 2 +- common/src/sys/buff.rs | 8 +- server/src/cmd.rs | 13 +- server/src/events/entity_manipulation.rs | 51 +- server/src/state_ext.rs | 14 +- server/src/sys/message.rs | 743 ----------------------- server/src/sys/object.rs | 16 +- voxygen/src/ecs/sys/floater.rs | 8 +- voxygen/src/hud/chat.rs | 8 +- 17 files changed, 120 insertions(+), 830 deletions(-) delete mode 100644 server/src/sys/message.rs diff --git a/assets/voxygen/i18n/de_DE.ron b/assets/voxygen/i18n/de_DE.ron index 329571443a..8448b4dc39 100644 --- a/assets/voxygen/i18n/de_DE.ron +++ b/assets/voxygen/i18n/de_DE.ron @@ -188,13 +188,14 @@ https://account.veloren.net. "hud.chat.pvp_ranged_kill_msg": "[{attacker}] hat [{victim}] aus der Ferne getötet", "hud.chat.pvp_explosion_kill_msg": "[{attacker}] hat [{victim}] hochgejagt", "hud.chat.pvp_energy_kill_msg": "[{attacker}] hat [{victim}] mit Magie erledigt", - "hud.chat.pvp_buff_kill_msg" : "[{attacker}] hat [{victim}] mit Magie erledigt", + "hud.chat.pvp_other_kill_msg" : "[{attacker}] hat [{victim}] mit Magie erledigt", "hud.chat.npc_melee_kill_msg": "{attacker} tötete [{victim}]", "hud.chat.npc_ranged_kill_msg": "{attacker} tötete [{victim}]", "hud.chat.npc_explosion_kill_msg": "{attacker} hat [{victim}] hochgejagt", - "hud.chat.npc_buff_kill_msg": "[{attacker}] tötete [{victim}]", + "hud.chat.npc_energy_kill_msg": "[{attacker}] hat [{victim}] mit Magie erledigt", + "hud.chat.npc_other_kill_msg": "[{attacker}] tötete [{victim}]", // SCT outputs "hud.sct.experience": "{amount} Erf", diff --git a/assets/voxygen/i18n/en.ron b/assets/voxygen/i18n/en.ron index 78c21cfa44..cb540505d5 100644 --- a/assets/voxygen/i18n/en.ron +++ b/assets/voxygen/i18n/en.ron @@ -184,13 +184,14 @@ https://account.veloren.net."#, "hud.chat.pvp_ranged_kill_msg": "[{attacker}] shot [{victim}]", "hud.chat.pvp_explosion_kill_msg": "[{attacker}] blew up [{victim}]", "hud.chat.pvp_energy_kill_msg": "[{attacker}] killed [{victim}] with magic", - "hud.chat.pvp_buff_kill_msg": "[{attacker}] killed [{victim}]", + "hud.chat.pvp_other_kill_msg": "[{attacker}] killed [{victim}]", "hud.chat.npc_melee_kill_msg": "{attacker} killed [{victim}]", "hud.chat.npc_ranged_kill_msg": "{attacker} shot [{victim}]", "hud.chat.npc_explosion_kill_msg": "{attacker} blew up [{victim}]", - "hud.chat.npc_buff_kill_msg": "[{attacker}] killed [{victim}]", + "hud.chat.npc_energy_kill_msg": "[{attacker}] killed [{victim}] with magic", + "hud.chat.npc_other_kill_msg": "[{attacker}] killed [{victim}]", "hud.chat.loot_msg": "You picked up [{item}]", "hud.chat.loot_fail": "Your Inventory is full!", diff --git a/assets/voxygen/i18n/tr_TR.ron b/assets/voxygen/i18n/tr_TR.ron index 13b801465b..1eb7166a63 100644 --- a/assets/voxygen/i18n/tr_TR.ron +++ b/assets/voxygen/i18n/tr_TR.ron @@ -184,13 +184,14 @@ bir hesap oluşturabilirsin."#, "hud.chat.pvp_ranged_kill_msg": "[{victim}], [{attacker}] tarafından vuruldu.", "hud.chat.pvp_explosion_kill_msg": "[{victim}], [{attacker}] tarafından havaya uçuruldu.", "hud.chat.pvp_energy_kill_msg": "[{victim}], [{attacker}] tarafından büyü ile mağlup edildi.", - "hud.chat.pvp_buff_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.", + "hud.chat.pvp_other_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.", "hud.chat.npc_melee_kill_msg": "[{victim}], [{attacker}] tarafından mağlup edildi.", "hud.chat.npc_ranged_kill_msg": "[{victim}], [{attacker}] tarafından vuruldu.", "hud.chat.npc_explosion_kill_msg": "[{victim}], [{attacker}] tarafından havaya uçuruldu.", - "hud.chat.npc_buff_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.", + "hud.chat.npc_energy_kill_msg": "[{victim}], [{attacker}] tarafından büyü ile mağlup edildi.", + "hud.chat.npc_other_kill_msg": "[{victim}], [{attacker}] tarafından öldürüldü.", "hud.chat.loot_msg": "[{item}] topladın.", "hud.chat.loot_fail": "Envanterin dolu!", diff --git a/client/src/lib.rs b/client/src/lib.rs index 8aa29e35f3..73efa29ee8 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -1733,7 +1733,7 @@ impl Client { alias_of_uid(attacker_uid), alias_of_uid(victim) ), - KillSource::Player(attacker_uid, KillType::Buff) => format!( + KillSource::Player(attacker_uid, KillType::Other) => format!( "[{}] killed [{}]", alias_of_uid(attacker_uid), alias_of_uid(victim) @@ -1752,7 +1752,7 @@ impl Client { attacker_name, alias_of_uid(victim) ), - KillSource::NonPlayer(attacker_name, KillType::Buff) => { + KillSource::NonPlayer(attacker_name, KillType::Other) => { format!("{} killed [{}]", attacker_name, alias_of_uid(victim)) }, KillSource::Environment(environment) => { @@ -1780,7 +1780,7 @@ impl Client { KillSource::Player(attacker_uid, KillType::Energy) => message .replace("{attacker}", &alias_of_uid(attacker_uid)) .replace("{victim}", &alias_of_uid(victim)), - KillSource::Player(attacker_uid, KillType::Buff) => message + KillSource::Player(attacker_uid, KillType::Other) => message .replace("{attacker}", &alias_of_uid(attacker_uid)) .replace("{victim}", &alias_of_uid(victim)), KillSource::NonPlayer(attacker_name, KillType::Melee) => message @@ -1795,7 +1795,7 @@ impl Client { KillSource::NonPlayer(attacker_name, KillType::Energy) => message .replace("{attacker}", attacker_name) .replace("{victim}", &alias_of_uid(victim)), - KillSource::NonPlayer(attacker_name, KillType::Buff) => message + KillSource::NonPlayer(attacker_name, KillType::Other) => message .replace("{attacker}", attacker_name) .replace("{victim}", &alias_of_uid(victim)), KillSource::Environment(environment) => message diff --git a/common/src/combat.rs b/common/src/combat.rs index 143761f500..91be46af58 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -12,7 +12,7 @@ pub enum GroupTarget { OutOfGroup, } -#[derive(Copy, Clone, Debug, Eq, PartialEq, Serialize, Deserialize)] +#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Serialize, Deserialize)] pub enum DamageSource { Melee, Healing, @@ -21,6 +21,7 @@ pub enum DamageSource { Falling, Shockwave, Energy, + Other, } #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] @@ -50,7 +51,10 @@ impl Damage { HealthChange { amount: -damage as i32, - cause: HealthSource::Attack { by: uid.unwrap() }, + cause: HealthSource::Damage { + kind: self.source, + by: uid, + }, } }, DamageSource::Projectile => { @@ -64,7 +68,10 @@ impl Damage { HealthChange { amount: -damage as i32, - cause: HealthSource::Projectile { owner: uid }, + cause: HealthSource::Damage { + kind: self.source, + by: uid, + }, } }, DamageSource::Explosion => { @@ -74,7 +81,10 @@ impl Damage { HealthChange { amount: -damage as i32, - cause: HealthSource::Explosion { owner: uid }, + cause: HealthSource::Damage { + kind: self.source, + by: uid, + }, } }, DamageSource::Shockwave => { @@ -84,7 +94,10 @@ impl Damage { HealthChange { amount: -damage as i32, - cause: HealthSource::Attack { by: uid.unwrap() }, + cause: HealthSource::Damage { + kind: self.source, + by: uid, + }, } }, DamageSource::Energy => { @@ -94,12 +107,15 @@ impl Damage { HealthChange { amount: -damage as i32, - cause: HealthSource::Energy { owner: uid }, + cause: HealthSource::Damage { + kind: self.source, + by: uid, + }, } }, DamageSource::Healing => HealthChange { amount: damage as i32, - cause: HealthSource::Healing { by: uid }, + cause: HealthSource::Heal { by: uid }, }, DamageSource::Falling => { // Armor @@ -112,6 +128,13 @@ impl Damage { cause: HealthSource::World, } }, + DamageSource::Other => HealthChange { + amount: -damage as i32, + cause: HealthSource::Damage { + kind: self.source, + by: uid, + }, + }, } } diff --git a/common/src/comp/chat.rs b/common/src/comp/chat.rs index 0b748db9d4..4d5b0f9590 100644 --- a/common/src/comp/chat.rs +++ b/common/src/comp/chat.rs @@ -51,7 +51,7 @@ pub enum KillType { Projectile, Explosion, Energy, - Buff, + Other, // Projectile(String), TODO: add projectile name when available } diff --git a/common/src/comp/health.rs b/common/src/comp/health.rs index a9309be24b..9cddb6ebe9 100644 --- a/common/src/comp/health.rs +++ b/common/src/comp/health.rs @@ -1,4 +1,4 @@ -use crate::{comp::Body, sync::Uid}; +use crate::{comp::Body, sync::Uid, DamageSource}; use serde::{Deserialize, Serialize}; use specs::{Component, FlaggedStorage}; use specs_idvs::IdvStorage; @@ -12,18 +12,20 @@ pub struct HealthChange { #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum HealthSource { - Attack { by: Uid }, // TODO: Implement weapon - Projectile { owner: Option }, - Explosion { owner: Option }, - Energy { owner: Option }, - Buff { owner: Option }, + Damage { kind: DamageSource, by: Option }, + Heal { by: Option }, + //Attack { by: Uid }, // TODO: Implement weapon + //Projectile { owner: Option }, + //Explosion { owner: Option }, + //Energy { owner: Option }, + //Buff { owner: Option }, Suicide, World, Revive, Command, LevelUp, Item, - Healing { by: Option }, + //Healing { by: Option }, Unknown, } diff --git a/common/src/sys/agent.rs b/common/src/sys/agent.rs index 27e4a8c600..ff9f04c52c 100644 --- a/common/src/sys/agent.rs +++ b/common/src/sys/agent.rs @@ -605,11 +605,7 @@ impl<'a> System<'a> for Sys { if let Some(my_health) = healths.get(entity) { // Only if the attack was recent if my_health.last_change.0 < 3.0 { - if let comp::HealthSource::Attack { by } - | comp::HealthSource::Projectile { owner: Some(by) } - | comp::HealthSource::Energy { owner: Some(by) } - | comp::HealthSource::Buff { owner: Some(by) } - | comp::HealthSource::Explosion { owner: Some(by) } = + if let comp::HealthSource::Damage { by: Some(by), .. } = my_health.last_change.1.cause { if !agent.activity.is_attack() { @@ -660,7 +656,8 @@ impl<'a> System<'a> for Sys { // Attack owner's attacker let owner_health = healths.get(owner)?; if owner_health.last_change.0 < 5.0 && owner_health.last_change.1.amount < 0 { - if let comp::HealthSource::Attack { by } = owner_health.last_change.1.cause + if let comp::HealthSource::Damage { by: Some(by), .. } = + owner_health.last_change.1.cause { if !agent.activity.is_attack() { let attacker = uid_allocator.retrieve_entity_internal(by.id())?; diff --git a/common/src/sys/beam.rs b/common/src/sys/beam.rs index 11156c74d6..0642e0e9f7 100644 --- a/common/src/sys/beam.rs +++ b/common/src/sys/beam.rs @@ -178,7 +178,7 @@ impl<'a> System<'a> for Sys { amount: (-change.amount as f32 * beam_segment.lifesteal_eff) as i32, - cause: HealthSource::Healing { + cause: HealthSource::Heal { by: beam_segment.owner, }, }, diff --git a/common/src/sys/buff.rs b/common/src/sys/buff.rs index 9cb76930b3..14011718bc 100644 --- a/common/src/sys/buff.rs +++ b/common/src/sys/buff.rs @@ -5,6 +5,7 @@ use crate::{ }, event::{EventBus, ServerEvent}, state::DeltaTime, + DamageSource, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; use std::time::Duration; @@ -86,9 +87,12 @@ impl<'a> System<'a> for Sys { || buff.time.map_or(false, |dur| dur == Duration::default()) { let cause = if *accumulated > 0.0 { - HealthSource::Healing { by: buff_owner } + HealthSource::Heal { by: buff_owner } } else { - HealthSource::Buff { owner: buff_owner } + HealthSource::Damage { + kind: DamageSource::Other, + by: buff_owner, + } }; server_emitter.emit(ServerEvent::Damage { entity, diff --git a/server/src/cmd.rs b/server/src/cmd.rs index daffafd1df..dc0236e4e3 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -19,7 +19,7 @@ use common::{ terrain::{Block, BlockKind, SpriteKind, TerrainChunkSize}, util::Dir, vol::RectVolSize, - Explosion, LoadoutBuilder, RadiusEffect, + Damage, DamageSource, Explosion, LoadoutBuilder, RadiusEffect, }; use rand::Rng; use specs::{Builder, Entity as EcsEntity, Join, WorldExt}; @@ -394,7 +394,10 @@ fn handle_kill( let reason = if client == target { comp::HealthSource::Suicide } else if let Some(uid) = server.state.read_storage::().get(client) { - comp::HealthSource::Attack { by: *uid } + comp::HealthSource::Damage { + kind: DamageSource::Other, + by: Some(*uid), + } } else { comp::HealthSource::Command }; @@ -1160,9 +1163,9 @@ fn handle_explosion( effects: vec![ RadiusEffect::Entity( None, - Effect::Health(comp::HealthChange { - amount: (-100.0 * power) as i32, - cause: comp::HealthSource::Explosion { owner: None }, + Effect::Damage(Damage { + source: DamageSource::Explosion, + value: 100.0 * power, }), ), RadiusEffect::TerrainDestruction(power), diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 7fb60ec268..a9f4aa83e6 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -73,7 +73,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc if let Some(_player) = state.ecs().read_storage::().get(entity) { if let Some(uid) = state.ecs().read_storage::().get(entity) { let kill_source = match cause { - HealthSource::Attack { by } => { + HealthSource::Damage { + kind: DamageSource::Melee, + by: Some(by), + } => { // Get attacker entity if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) { // Check if attacker is another player or entity with stats (npc) @@ -95,7 +98,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc KillSource::NonPlayer("".to_string(), KillType::Melee) } }, - HealthSource::Projectile { owner: Some(by) } => { + HealthSource::Damage { + kind: DamageSource::Projectile, + by: Some(by), + } => { // Get projectile owner entity TODO: add names to projectiles and send in // message if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) { @@ -118,7 +124,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc KillSource::NonPlayer("".to_string(), KillType::Projectile) } }, - HealthSource::Explosion { owner: Some(by) } => { + HealthSource::Damage { + kind: DamageSource::Explosion, + by: Some(by), + } => { // Get explosion owner entity if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) { // Check if attacker is another player or entity with stats (npc) @@ -140,7 +149,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc KillSource::NonPlayer("".to_string(), KillType::Explosion) } }, - HealthSource::Energy { owner: Some(by) } => { + HealthSource::Damage { + kind: DamageSource::Energy, + by: Some(by), + } => { // Get energy owner entity if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) { // Check if attacker is another player or entity with stats (npc) @@ -162,7 +174,10 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc KillSource::NonPlayer("".to_string(), KillType::Energy) } }, - HealthSource::Buff { owner: Some(by) } => { + HealthSource::Damage { + kind: DamageSource::Other, + by: Some(by), + } => { // Get energy owner entity if let Some(char_entity) = state.ecs().entity_from_uid(by.into()) { // Check if attacker is another player or entity with stats (npc) @@ -172,29 +187,26 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc .get(char_entity) .is_some() { - KillSource::Player(by, KillType::Buff) + KillSource::Player(by, KillType::Other) } else if let Some(stats) = state.ecs().read_storage::().get(char_entity) { - KillSource::NonPlayer(stats.name.clone(), KillType::Buff) + KillSource::NonPlayer(stats.name.clone(), KillType::Other) } else { - KillSource::NonPlayer("".to_string(), KillType::Buff) + KillSource::NonPlayer("".to_string(), KillType::Other) } } else { - KillSource::NonPlayer("".to_string(), KillType::Buff) + KillSource::NonPlayer("".to_string(), KillType::Other) } }, HealthSource::World => KillSource::FallDamage, HealthSource::Suicide => KillSource::Suicide, - HealthSource::Projectile { owner: None } - | HealthSource::Explosion { owner: None } - | HealthSource::Energy { owner: None } - | HealthSource::Buff { owner: None } + HealthSource::Damage { .. } | HealthSource::Revive | HealthSource::Command | HealthSource::LevelUp | HealthSource::Item - | HealthSource::Healing { by: _ } + | HealthSource::Heal { by: _ } | HealthSource::Unknown => KillSource::Other, }; state @@ -205,12 +217,7 @@ pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSourc // Give EXP to the killer if entity had stats (|| { let mut stats = state.ecs().write_storage::(); - let by = if let HealthSource::Attack { by } - | HealthSource::Projectile { owner: Some(by) } - | HealthSource::Energy { owner: Some(by) } - | HealthSource::Buff { owner: Some(by) } - | HealthSource::Explosion { owner: Some(by) } = cause - { + let by = if let HealthSource::Damage { by: Some(by), .. } = cause { by } else { return; @@ -516,7 +523,7 @@ pub fn handle_explosion( // Uses radius as outcome power, makes negative if explosion has healing effect let outcome_power = explosion.radius * if explosion.effects.iter().any( - |e| matches!(e, RadiusEffect::Entity(_, e) if matches!(e, Effect::Damage(d) if matches!(d.source, DamageSource::Healing))), + |e| matches!(e, RadiusEffect::Entity(_, Effect::Damage(Damage { source: DamageSource::Healing, .. }))) ) { -1.0 } else { @@ -530,7 +537,7 @@ pub fn handle_explosion( is_attack: explosion .effects .iter() - .any(|e| matches!(e, RadiusEffect::Entity(_, e) if matches!(e, Effect::Damage(_)))), + .any(|e| matches!(e, RadiusEffect::Entity(_, Effect::Damage(_)))), reagent, }); let owner_entity = owner.and_then(|uid| { diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index e10144f8ee..642c4d427c 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -86,14 +86,12 @@ impl StateExt for State { .map(|stats| stats.exp.change_by(xp)); }, Effect::Damage(damage) => { - #[allow(irrefutable_let_patterns)] - if let loadout = self.ecs().read_storage::().get(entity) { - let change = damage.modify_damage(loadout, source); - self.ecs() - .write_storage::() - .get_mut(entity) - .map(|health| health.change_by(change)); - } + let loadouts = self.ecs().read_storage::(); + let change = damage.modify_damage(loadouts.get(entity), source); + self.ecs() + .write_storage::() + .get_mut(entity) + .map(|health| health.change_by(change)); }, } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs deleted file mode 100644 index 5fdb00985d..0000000000 --- a/server/src/sys/message.rs +++ /dev/null @@ -1,743 +0,0 @@ -use super::SysTimer; -use crate::{ - alias_validator::AliasValidator, - character_creator, - client::Client, - login_provider::LoginProvider, - metrics::{NetworkRequestMetrics, PlayerMetrics}, - persistence::character_loader::CharacterLoader, - EditableSettings, Settings, -}; -use common::{ - comp::{ - Admin, CanBuild, ChatMode, ChatType, ControlEvent, Controller, ForceUpdate, Health, Ori, - Player, Pos, Stats, UnresolvedChatMsg, Vel, - }, - event::{EventBus, ServerEvent}, - msg::{ - validate_chat_msg, CharacterInfo, ChatMsgValidationError, ClientGeneral, ClientInGame, - ClientRegister, DisconnectReason, PingMsg, PlayerInfo, PlayerListUpdate, RegisterError, - ServerGeneral, ServerRegisterAnswer, MAX_BYTES_CHAT_MSG, - }, - span, - state::{BlockChange, Time}, - sync::Uid, - terrain::{TerrainChunkSize, TerrainGrid}, - vol::{ReadVol, RectVolSize}, -}; -use futures_executor::block_on; -use futures_timer::Delay; -use futures_util::{select, FutureExt}; -use hashbrown::HashMap; -use specs::{ - Entities, Join, Read, ReadExpect, ReadStorage, System, Write, WriteExpect, WriteStorage, -}; -use tracing::{debug, error, info, trace, warn}; - -impl Sys { - #[allow(clippy::too_many_arguments)] - fn handle_client_msg( - server_emitter: &mut common::event::Emitter<'_, ServerEvent>, - new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, - entity: specs::Entity, - client: &mut Client, - player_metrics: &ReadExpect<'_, PlayerMetrics>, - uids: &ReadStorage<'_, Uid>, - chat_modes: &ReadStorage<'_, ChatMode>, - msg: ClientGeneral, - ) -> Result<(), crate::error::Error> { - match msg { - ClientGeneral::ChatMsg(message) => { - if client.registered { - match validate_chat_msg(&message) { - Ok(()) => { - if let Some(from) = uids.get(entity) { - let mode = chat_modes.get(entity).cloned().unwrap_or_default(); - let msg = mode.new_message(*from, message); - new_chat_msgs.push((Some(entity), msg)); - } else { - error!("Could not send message. Missing player uid"); - } - }, - Err(ChatMsgValidationError::TooLong) => { - let max = MAX_BYTES_CHAT_MSG; - let len = message.len(); - warn!(?len, ?max, "Received a chat message that's too long") - }, - } - } - }, - ClientGeneral::Disconnect => { - client.send_msg(ServerGeneral::Disconnect(DisconnectReason::Requested)); - }, - ClientGeneral::Terminate => { - debug!(?entity, "Client send message to termitate session"); - player_metrics - .clients_disconnected - .with_label_values(&["gracefully"]) - .inc(); - server_emitter.emit(ServerEvent::ClientDisconnect(entity)); - }, - _ => unreachable!("not a client_general msg"), - } - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - fn handle_client_in_game_msg( - server_emitter: &mut common::event::Emitter<'_, ServerEvent>, - entity: specs::Entity, - client: &mut Client, - terrain: &ReadExpect<'_, TerrainGrid>, - network_metrics: &ReadExpect<'_, NetworkRequestMetrics>, - can_build: &ReadStorage<'_, CanBuild>, - force_updates: &ReadStorage<'_, ForceUpdate>, - stats: &mut WriteStorage<'_, Stats>, - healths: &mut WriteStorage<'_, Health>, - block_changes: &mut Write<'_, BlockChange>, - positions: &mut WriteStorage<'_, Pos>, - velocities: &mut WriteStorage<'_, Vel>, - orientations: &mut WriteStorage<'_, Ori>, - players: &mut WriteStorage<'_, Player>, - controllers: &mut WriteStorage<'_, Controller>, - settings: &Read<'_, Settings>, - msg: ClientGeneral, - ) -> Result<(), crate::error::Error> { - if client.in_game.is_none() { - debug!(?entity, "client is not in_game, ignoring msg"); - trace!(?msg, "ignored msg content"); - if matches!(msg, ClientGeneral::TerrainChunkRequest{ .. }) { - network_metrics.chunks_request_dropped.inc(); - } - return Ok(()); - } - match msg { - // Go back to registered state (char selection screen) - ClientGeneral::ExitInGame => { - client.in_game = None; - server_emitter.emit(ServerEvent::ExitIngame { entity }); - client.send_msg(ServerGeneral::ExitInGameSuccess); - }, - ClientGeneral::SetViewDistance(view_distance) => { - players.get_mut(entity).map(|player| { - player.view_distance = Some( - settings - .max_view_distance - .map(|max| view_distance.min(max)) - .unwrap_or(view_distance), - ) - }); - - //correct client if its VD is to high - if settings - .max_view_distance - .map(|max| view_distance > max) - .unwrap_or(false) - { - client.send_msg(ServerGeneral::SetViewDistance( - settings.max_view_distance.unwrap_or(0), - )); - } - }, - ClientGeneral::ControllerInputs(inputs) => { - if let Some(ClientInGame::Character) = client.in_game { - if let Some(controller) = controllers.get_mut(entity) { - controller.inputs.update_with_new(inputs); - } - } - }, - ClientGeneral::ControlEvent(event) => { - if let Some(ClientInGame::Character) = client.in_game { - // Skip respawn if client entity is alive - if let ControlEvent::Respawn = event { - if healths.get(entity).map_or(true, |h| !h.is_dead) { - //Todo: comment why return! - return Ok(()); - } - } - if let Some(controller) = controllers.get_mut(entity) { - controller.events.push(event); - } - } - }, - ClientGeneral::ControlAction(event) => { - if let Some(ClientInGame::Character) = client.in_game { - if let Some(controller) = controllers.get_mut(entity) { - controller.actions.push(event); - } - } - }, - ClientGeneral::PlayerPhysics { pos, vel, ori } => { - if let Some(ClientInGame::Character) = client.in_game { - if force_updates.get(entity).is_none() - && healths.get(entity).map_or(true, |h| !h.is_dead) - { - let _ = positions.insert(entity, pos); - let _ = velocities.insert(entity, vel); - let _ = orientations.insert(entity, ori); - } - } - }, - ClientGeneral::BreakBlock(pos) => { - if let Some(block) = can_build.get(entity).and_then(|_| terrain.get(pos).ok()) { - block_changes.set(pos, block.into_vacant()); - } - }, - ClientGeneral::PlaceBlock(pos, block) => { - if can_build.get(entity).is_some() { - block_changes.try_set(pos, block); - } - }, - ClientGeneral::TerrainChunkRequest { key } => { - let in_vd = if let (Some(view_distance), Some(pos)) = ( - players.get(entity).and_then(|p| p.view_distance), - positions.get(entity), - ) { - pos.0.xy().map(|e| e as f64).distance( - key.map(|e| e as f64 + 0.5) * TerrainChunkSize::RECT_SIZE.map(|e| e as f64), - ) < (view_distance as f64 - 1.0 + 2.5 * 2.0_f64.sqrt()) - * TerrainChunkSize::RECT_SIZE.x as f64 - } else { - true - }; - if in_vd { - match terrain.get_key(key) { - Some(chunk) => { - network_metrics.chunks_served_from_memory.inc(); - client.send_msg(ServerGeneral::TerrainChunkUpdate { - key, - chunk: Ok(Box::new(chunk.clone())), - }) - }, - None => { - network_metrics.chunks_generation_triggered.inc(); - server_emitter.emit(ServerEvent::ChunkRequest(entity, key)) - }, - } - } else { - network_metrics.chunks_request_dropped.inc(); - } - }, - ClientGeneral::UnlockSkill(skill) => { - stats - .get_mut(entity) - .map(|s| s.skill_set.unlock_skill(skill)); - }, - ClientGeneral::RefundSkill(skill) => { - stats - .get_mut(entity) - .map(|s| s.skill_set.refund_skill(skill)); - }, - ClientGeneral::UnlockSkillGroup(skill_group_type) => { - stats - .get_mut(entity) - .map(|s| s.skill_set.unlock_skill_group(skill_group_type)); - }, - _ => unreachable!("not a client_in_game msg"), - } - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - fn handle_client_character_screen_msg( - server_emitter: &mut common::event::Emitter<'_, ServerEvent>, - new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, - entity: specs::Entity, - client: &mut Client, - character_loader: &ReadExpect<'_, CharacterLoader>, - uids: &ReadStorage<'_, Uid>, - players: &mut WriteStorage<'_, Player>, - editable_settings: &ReadExpect<'_, EditableSettings>, - alias_validator: &ReadExpect<'_, AliasValidator>, - msg: ClientGeneral, - ) -> Result<(), crate::error::Error> { - match msg { - // Request spectator state - ClientGeneral::Spectate if client.registered => { - client.in_game = Some(ClientInGame::Spectator) - }, - ClientGeneral::Spectate => debug!("dropped Spectate msg from unregistered client"), - ClientGeneral::Character(character_id) - if client.registered && client.in_game.is_none() => - { - if let Some(player) = players.get(entity) { - // Send a request to load the character's component data from the - // DB. Once loaded, persisted components such as stats and inventory - // will be inserted for the entity - character_loader.load_character_data( - entity, - player.uuid().to_string(), - character_id, - ); - - // Start inserting non-persisted/default components for the entity - // while we load the DB data - server_emitter.emit(ServerEvent::InitCharacterData { - entity, - character_id, - }); - - // Give the player a welcome message - if !editable_settings.server_description.is_empty() { - client.send_msg( - ChatType::CommandInfo - .server_msg(String::from(&*editable_settings.server_description)), - ); - } - - if !client.login_msg_sent { - if let Some(player_uid) = uids.get(entity) { - new_chat_msgs.push((None, UnresolvedChatMsg { - chat_type: ChatType::Online(*player_uid), - message: "".to_string(), - })); - - client.login_msg_sent = true; - } - } - } else { - client.send_msg(ServerGeneral::CharacterDataLoadError(String::from( - "Failed to fetch player entity", - ))) - } - } - ClientGeneral::Character(_) => { - let registered = client.registered; - let in_game = client.in_game; - debug!(?registered, ?in_game, "dropped Character msg from client"); - }, - ClientGeneral::RequestCharacterList => { - if let Some(player) = players.get(entity) { - character_loader.load_character_list(entity, player.uuid().to_string()) - } - }, - ClientGeneral::CreateCharacter { alias, tool, body } => { - if let Err(error) = alias_validator.validate(&alias) { - debug!(?error, ?alias, "denied alias as it contained a banned word"); - client.send_msg(ServerGeneral::CharacterActionError(error.to_string())); - } else if let Some(player) = players.get(entity) { - character_creator::create_character( - entity, - player.uuid().to_string(), - alias, - tool, - body, - character_loader, - ); - } - }, - ClientGeneral::DeleteCharacter(character_id) => { - if let Some(player) = players.get(entity) { - character_loader.delete_character( - entity, - player.uuid().to_string(), - character_id, - ); - } - }, - _ => unreachable!("not a client_character_screen msg"), - } - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - fn handle_ping_msg(client: &mut Client, msg: PingMsg) -> Result<(), crate::error::Error> { - match msg { - PingMsg::Ping => client.send_msg(PingMsg::Pong), - PingMsg::Pong => {}, - } - Ok(()) - } - - #[allow(clippy::too_many_arguments)] - fn handle_register_msg( - player_list: &HashMap, - new_players: &mut Vec, - entity: specs::Entity, - client: &mut Client, - player_metrics: &ReadExpect<'_, PlayerMetrics>, - login_provider: &mut WriteExpect<'_, LoginProvider>, - admins: &mut WriteStorage<'_, Admin>, - players: &mut WriteStorage<'_, Player>, - editable_settings: &ReadExpect<'_, EditableSettings>, - msg: ClientRegister, - ) -> Result<(), crate::error::Error> { - let (username, uuid) = match login_provider.try_login( - &msg.token_or_username, - &*editable_settings.admins, - &*editable_settings.whitelist, - &*editable_settings.banlist, - ) { - Err(err) => { - client - .register_stream - .send(ServerRegisterAnswer::Err(err))?; - return Ok(()); - }, - Ok((username, uuid)) => (username, uuid), - }; - - const INITIAL_VD: Option = Some(5); //will be changed after login - let player = Player::new(username, None, INITIAL_VD, uuid); - let is_admin = editable_settings.admins.contains(&uuid); - - if !player.is_valid() { - // Invalid player - client - .register_stream - .send(ServerRegisterAnswer::Err(RegisterError::InvalidCharacter))?; - return Ok(()); - } - - if !client.registered && client.in_game.is_none() { - // Add Player component to this client - let _ = players.insert(entity, player); - player_metrics.players_connected.inc(); - - // Give the Admin component to the player if their name exists in - // admin list - if is_admin { - let _ = admins.insert(entity, Admin); - } - - // Tell the client its request was successful. - client.registered = true; - client.register_stream.send(ServerRegisterAnswer::Ok(()))?; - - // Send initial player list - client.send_msg(ServerGeneral::PlayerListUpdate(PlayerListUpdate::Init( - player_list.clone(), - ))); - - // Add to list to notify all clients of the new player - new_players.push(entity); - } - Ok(()) - } - - ///We needed to move this to a async fn, if we would use a async closures - /// the compiler generates to much recursion and fails to compile this - #[allow(clippy::too_many_arguments)] - async fn handle_messages( - server_emitter: &mut common::event::Emitter<'_, ServerEvent>, - new_chat_msgs: &mut Vec<(Option, UnresolvedChatMsg)>, - player_list: &HashMap, - new_players: &mut Vec, - entity: specs::Entity, - client: &mut Client, - cnt: &mut u64, - character_loader: &ReadExpect<'_, CharacterLoader>, - terrain: &ReadExpect<'_, TerrainGrid>, - network_metrics: &ReadExpect<'_, NetworkRequestMetrics>, - player_metrics: &ReadExpect<'_, PlayerMetrics>, - uids: &ReadStorage<'_, Uid>, - can_build: &ReadStorage<'_, CanBuild>, - force_updates: &ReadStorage<'_, ForceUpdate>, - stats: &mut WriteStorage<'_, Stats>, - healths: &mut WriteStorage<'_, Health>, - chat_modes: &ReadStorage<'_, ChatMode>, - login_provider: &mut WriteExpect<'_, LoginProvider>, - block_changes: &mut Write<'_, BlockChange>, - admins: &mut WriteStorage<'_, Admin>, - positions: &mut WriteStorage<'_, Pos>, - velocities: &mut WriteStorage<'_, Vel>, - orientations: &mut WriteStorage<'_, Ori>, - players: &mut WriteStorage<'_, Player>, - controllers: &mut WriteStorage<'_, Controller>, - settings: &Read<'_, Settings>, - editable_settings: &ReadExpect<'_, EditableSettings>, - alias_validator: &ReadExpect<'_, AliasValidator>, - ) -> Result<(), crate::error::Error> { - let (mut b1, mut b2, mut b3, mut b4, mut b5) = ( - client.network_error, - client.network_error, - client.network_error, - client.network_error, - client.network_error, - ); - loop { - /* - waiting for 1 of the 5 streams to return a massage asynchronous. - If so, handle that msg type. This code will be refactored soon - */ - - let q1 = Client::internal_recv(&mut b1, &mut client.general_stream); - let q2 = Client::internal_recv(&mut b2, &mut client.in_game_stream); - let q3 = Client::internal_recv(&mut b3, &mut client.character_screen_stream); - let q4 = Client::internal_recv(&mut b4, &mut client.ping_stream); - let q5 = Client::internal_recv(&mut b5, &mut client.register_stream); - - let (m1, m2, m3, m4, m5) = select!( - msg = q1.fuse() => (Some(msg), None, None, None, None), - msg = q2.fuse() => (None, Some(msg), None, None, None), - msg = q3.fuse() => (None, None, Some(msg), None, None), - msg = q4.fuse() => (None, None, None, Some(msg), None), - msg = q5.fuse() => (None, None, None, None,Some(msg)), - ); - *cnt += 1; - if let Some(msg) = m1 { - client.network_error |= b1; - Self::handle_client_msg( - server_emitter, - new_chat_msgs, - entity, - client, - player_metrics, - uids, - chat_modes, - msg?, - )?; - } - if let Some(msg) = m2 { - client.network_error |= b2; - Self::handle_client_in_game_msg( - server_emitter, - entity, - client, - terrain, - network_metrics, - can_build, - force_updates, - stats, - healths, - block_changes, - positions, - velocities, - orientations, - players, - controllers, - settings, - msg?, - )?; - } - if let Some(msg) = m3 { - client.network_error |= b3; - Self::handle_client_character_screen_msg( - server_emitter, - new_chat_msgs, - entity, - client, - character_loader, - uids, - players, - editable_settings, - alias_validator, - msg?, - )?; - } - if let Some(msg) = m4 { - client.network_error |= b4; - Self::handle_ping_msg(client, msg?)?; - } - if let Some(msg) = m5 { - client.network_error |= b5; - Self::handle_register_msg( - player_list, - new_players, - entity, - client, - player_metrics, - login_provider, - admins, - players, - editable_settings, - msg?, - )?; - } - } - } -} - -/// This system will handle new messages from clients -pub struct Sys; -impl<'a> System<'a> for Sys { - #[allow(clippy::type_complexity)] // TODO: Pending review in #587 - type SystemData = ( - Entities<'a>, - Read<'a, EventBus>, - Read<'a, Time>, - ReadExpect<'a, CharacterLoader>, - ReadExpect<'a, TerrainGrid>, - ReadExpect<'a, NetworkRequestMetrics>, - ReadExpect<'a, PlayerMetrics>, - Write<'a, SysTimer>, - ReadStorage<'a, Uid>, - ReadStorage<'a, CanBuild>, - ReadStorage<'a, ForceUpdate>, - WriteStorage<'a, Stats>, - WriteStorage<'a, Health>, - ReadStorage<'a, ChatMode>, - WriteExpect<'a, LoginProvider>, - Write<'a, BlockChange>, - WriteStorage<'a, Admin>, - WriteStorage<'a, Pos>, - WriteStorage<'a, Vel>, - WriteStorage<'a, Ori>, - WriteStorage<'a, Player>, - WriteStorage<'a, Client>, - WriteStorage<'a, Controller>, - Read<'a, Settings>, - ReadExpect<'a, EditableSettings>, - ReadExpect<'a, AliasValidator>, - ); - - #[allow(clippy::match_ref_pats)] // TODO: Pending review in #587 - #[allow(clippy::single_char_pattern)] // TODO: Pending review in #587 - #[allow(clippy::single_match)] // TODO: Pending review in #587 - fn run( - &mut self, - ( - entities, - server_event_bus, - time, - character_loader, - terrain, - network_metrics, - player_metrics, - mut timer, - uids, - can_build, - force_updates, - mut stats, - mut healths, - chat_modes, - mut accounts, - mut block_changes, - mut admins, - mut positions, - mut velocities, - mut orientations, - mut players, - mut clients, - mut controllers, - settings, - editable_settings, - alias_validator, - ): Self::SystemData, - ) { - span!(_guard, "run", "message::Sys::run"); - timer.start(); - - let mut server_emitter = server_event_bus.emitter(); - - let mut new_chat_msgs = Vec::new(); - - // Player list to send new players. - let player_list = (&uids, &players, stats.maybe(), admins.maybe()) - .join() - .map(|(uid, player, stats, admin)| { - (*uid, PlayerInfo { - is_online: true, - is_admin: admin.is_some(), - player_alias: player.alias.clone(), - character: stats.map(|stats| CharacterInfo { - name: stats.name.clone(), - level: stats.level.level(), - }), - }) - }) - .collect::>(); - // List of new players to update player lists of all clients. - let mut new_players = Vec::new(); - - for (entity, client) in (&entities, &mut clients).join() { - let mut cnt = 0; - - let network_err: Result<(), crate::error::Error> = block_on(async { - //TIMEOUT 0.02 ms for msg handling - let work_future = Self::handle_messages( - &mut server_emitter, - &mut new_chat_msgs, - &player_list, - &mut new_players, - entity, - client, - &mut cnt, - &character_loader, - &terrain, - &network_metrics, - &player_metrics, - &uids, - &can_build, - &force_updates, - &mut stats, - &mut healths, - &chat_modes, - &mut accounts, - &mut block_changes, - &mut admins, - &mut positions, - &mut velocities, - &mut orientations, - &mut players, - &mut controllers, - &settings, - &editable_settings, - &alias_validator, - ); - select!( - _ = Delay::new(std::time::Duration::from_micros(20)).fuse() => Ok(()), - err = work_future.fuse() => err, - ) - }); - - // Network error - if network_err.is_err() { - debug!(?entity, "postbox error with client, disconnecting"); - player_metrics - .clients_disconnected - .with_label_values(&["network_error"]) - .inc(); - server_emitter.emit(ServerEvent::ClientDisconnect(entity)); - } else if cnt > 0 { - // Update client ping. - client.last_ping = time.0 - } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 - // Timeout - { - info!(?entity, "timeout error with client, disconnecting"); - player_metrics - .clients_disconnected - .with_label_values(&["timeout"]) - .inc(); - server_emitter.emit(ServerEvent::ClientDisconnect(entity)); - } else if time.0 - client.last_ping > settings.client_timeout.as_secs() as f64 * 0.5 { - // Try pinging the client if the timeout is nearing. - client.send_msg(PingMsg::Ping); - } - } - - // Handle new players. - // Tell all clients to add them to the player list. - for entity in new_players { - if let (Some(uid), Some(player)) = (uids.get(entity), players.get(entity)) { - let msg = - ServerGeneral::PlayerListUpdate(PlayerListUpdate::Add(*uid, PlayerInfo { - player_alias: player.alias.clone(), - is_online: true, - is_admin: admins.get(entity).is_some(), - character: None, // new players will be on character select. - })); - for client in (&mut clients).join().filter(|c| c.registered) { - client.send_msg(msg.clone()) - } - } - } - - // Handle new chat messages. - for (entity, msg) in new_chat_msgs { - // Handle chat commands. - if msg.message.starts_with("/") { - if let (Some(entity), true) = (entity, msg.message.len() > 1) { - let argv = String::from(&msg.message[1..]); - server_emitter.emit(ServerEvent::ChatCmd(entity, argv)); - } - } else { - // Send chat message - server_emitter.emit(ServerEvent::Chat(msg)); - } - } - - timer.end() - } -} diff --git a/server/src/sys/object.rs b/server/src/sys/object.rs index 519e7d5cad..c5f9c53804 100644 --- a/server/src/sys/object.rs +++ b/server/src/sys/object.rs @@ -1,10 +1,10 @@ use common::{ - comp::{HealthChange, HealthSource, Object, PhysicsState, Pos, Vel}, + comp::{HealthSource, Object, PhysicsState, Pos, Vel}, effect::Effect, event::{EventBus, ServerEvent}, span, state::DeltaTime, - Explosion, RadiusEffect, + Damage, DamageSource, Explosion, RadiusEffect, }; use specs::{Entities, Join, Read, ReadStorage, System, WriteStorage}; @@ -52,9 +52,9 @@ impl<'a> System<'a> for Sys { effects: vec![ RadiusEffect::Entity( None, - Effect::Health(HealthChange { - amount: -500, - cause: HealthSource::Explosion { owner: *owner }, + Effect::Damage(Damage { + source: DamageSource::Explosion, + value: 500.0, }), ), RadiusEffect::TerrainDestruction(4.0), @@ -79,9 +79,9 @@ impl<'a> System<'a> for Sys { effects: vec![ RadiusEffect::Entity( None, - Effect::Health(HealthChange { - amount: -50, - cause: HealthSource::Explosion { owner: *owner }, + Effect::Damage(Damage { + source: DamageSource::Explosion, + value: 50.0, }), ), RadiusEffect::TerrainDestruction(4.0), diff --git a/voxygen/src/ecs/sys/floater.rs b/voxygen/src/ecs/sys/floater.rs index 4947ddc560..ac2a7a0863 100644 --- a/voxygen/src/ecs/sys/floater.rs +++ b/voxygen/src/ecs/sys/floater.rs @@ -81,12 +81,8 @@ impl<'a> System<'a> for Sys { // (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) } => { + HealthSource::Damage { by: Some(by), .. } + | HealthSource::Heal { 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 { diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index b9424d366a..36f3f7e94f 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -373,9 +373,9 @@ impl<'a> Widget for Chat<'a> { .localized_strings .get("hud.chat.pvp_energy_kill_msg") .to_string(), - KillSource::Player(_, KillType::Buff) => self + KillSource::Player(_, KillType::Other) => self .localized_strings - .get("hud.chat.pvp_buff_kill_msg") + .get("hud.chat.pvp_other_kill_msg") .to_string(), KillSource::NonPlayer(_, KillType::Melee) => self .localized_strings @@ -393,9 +393,9 @@ impl<'a> Widget for Chat<'a> { .localized_strings .get("hud.chat.npc_energy_kill_msg") .to_string(), - KillSource::NonPlayer(_, KillType::Buff) => self + KillSource::NonPlayer(_, KillType::Other) => self .localized_strings - .get("hud.chat.npc_buff_kill_msg") + .get("hud.chat.npc_other_kill_msg") .to_string(), KillSource::Environment(_) => self .localized_strings From f69f494524edf8c20f74c44bf447d25a24d68ee8 Mon Sep 17 00:00:00 2001 From: Sam Date: Wed, 4 Nov 2020 21:48:59 -0600 Subject: [PATCH 9/9] Fixed crash from changes in explosion code. --- server/src/events/entity_manipulation.rs | 37 +++++++++++++----------- 1 file changed, 20 insertions(+), 17 deletions(-) diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index a9f4aa83e6..91717fd532 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -615,12 +615,7 @@ pub fn handle_explosion( } }, RadiusEffect::Entity(target, mut effect) => { - for (entity_b, pos_b, health_b) in ( - &ecs.entities(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ) - .join() + for (entity_b, pos_b) in (&ecs.entities(), &ecs.read_storage::()).join() { // See if entities are in the same group let mut same_group = owner_entity @@ -646,17 +641,25 @@ pub fn handle_explosion( let distance_squared = pos.distance_squared(pos_b.0); let strength = 1.0 - distance_squared / explosion.radius.powi(2); - if strength > 0.0 && !health_b.is_dead { - effect.modify_strength(strength); - server.state().apply_effect(entity_b, effect, owner); - // Apply energy change - if let Some(owner) = owner_entity { - if let Some(energy) = ecs.write_storage::().get_mut(owner) - { - energy.change_by(EnergyChange { - amount: explosion.energy_regen as i32, - source: comp::EnergySource::HitEnemy, - }); + if strength > 0.0 { + let is_alive = ecs + .read_storage::() + .get(entity_b) + .map_or(false, |h| !h.is_dead); + + if is_alive { + effect.modify_strength(strength); + server.state().apply_effect(entity_b, effect, owner); + // Apply energy change + if let Some(owner) = owner_entity { + if let Some(energy) = + ecs.write_storage::().get_mut(owner) + { + energy.change_by(EnergyChange { + amount: explosion.energy_regen as i32, + source: comp::EnergySource::HitEnemy, + }); + } } } }