diff --git a/assets/common/abilities/axe/doublestrike.ron b/assets/common/abilities/axe/doublestrike.ron index 59172a0787..fa72fdc605 100644 --- a/assets/common/abilities/axe/doublestrike.ron +++ b/assets/common/abilities/axe/doublestrike.ron @@ -1,56 +1,69 @@ -ComboMelee( - stage_data: [ - ( - stage: 1, - base_damage: 11.0, - base_poise_damage: 12, - damage_increase: 1.0, - poise_damage_increase: 0, - knockback: 5.0, - range: 3.5, - angle: 50.0, - base_buildup_duration: 0.15, - base_swing_duration: 0.075, - hit_timing: 0.6, - base_recover_duration: 0.35, - forward_movement: 0.5, - damage_kind: Slashing, - damage_effect: Some(Buff(( - kind: Bleeding, - dur_secs: 10.0, - strength: DamageFraction(0.1), - chance: 0.1, - ))), - ), - ( - stage: 2, - base_damage: 13.0, - base_poise_damage: 20, - damage_increase: 1.5, - poise_damage_increase: 0, - knockback: 6.0, - range: 3.5, - angle: 30.0, - base_buildup_duration: 0.2, - base_swing_duration: 0.1, - hit_timing: 0.6, - base_recover_duration: 0.35, - forward_movement: 0.25, - damage_kind: Slashing, - damage_effect: Some(Buff(( - kind: Bleeding, - dur_secs: 10.0, - strength: DamageFraction(0.1), - chance: 0.1, - ))), - ), - ], - initial_energy_gain: 2.5, - max_energy_gain: 17.5, - energy_increase: 3.0, - speed_increase: 0.1, - max_speed_increase: 0.6, - scales_from_combo: 2, - is_interruptible: false, - ori_modifier: 1.0, +// ComboMelee( +// stage_data: [ +// ( +// stage: 1, +// base_damage: 11.0, +// base_poise_damage: 12, +// damage_increase: 1.0, +// poise_damage_increase: 0, +// knockback: 5.0, +// range: 3.5, +// angle: 50.0, +// base_buildup_duration: 0.15, +// base_swing_duration: 0.075, +// hit_timing: 0.6, +// base_recover_duration: 0.35, +// forward_movement: 0.5, +// damage_kind: Slashing, +// damage_effect: Some(Buff(( +// kind: Bleeding, +// dur_secs: 10.0, +// strength: DamageFraction(0.1), +// chance: 0.1, +// ))), +// ), +// ( +// stage: 2, +// base_damage: 13.0, +// base_poise_damage: 20, +// damage_increase: 1.5, +// poise_damage_increase: 0, +// knockback: 6.0, +// range: 3.5, +// angle: 30.0, +// base_buildup_duration: 0.2, +// base_swing_duration: 0.1, +// hit_timing: 0.6, +// base_recover_duration: 0.35, +// forward_movement: 0.25, +// damage_kind: Slashing, +// damage_effect: Some(Buff(( +// kind: Bleeding, +// dur_secs: 10.0, +// strength: DamageFraction(0.1), +// chance: 0.1, +// ))), +// ), +// ], +// initial_energy_gain: 2.5, +// max_energy_gain: 17.5, +// energy_increase: 3.0, +// speed_increase: 0.1, +// max_speed_increase: 0.6, +// scales_from_combo: 2, +// is_interruptible: false, +// ori_modifier: 1.0, +// ) +BasicMelee( + energy_cost: 0, + buildup_duration: 0.1, + swing_duration: 0.05, + recover_duration: 0.1, + base_damage: 10.0, + base_poise_damage: 0, + knockback: ( strength: 0.0, direction: Away), + range: 5.0, + max_angle: 45.0, + damage_effect: None, + damage_kind: Slashing, ) diff --git a/assets/common/abilities/hammer/singlestrike.ron b/assets/common/abilities/hammer/singlestrike.ron index b5b0d54b84..c5d207a06d 100644 --- a/assets/common/abilities/hammer/singlestrike.ron +++ b/assets/common/abilities/hammer/singlestrike.ron @@ -1,26 +1,39 @@ -ComboMelee( - stage_data: [( - stage: 1, - base_damage: 15.0, - damage_increase: 1.0, - base_poise_damage: 20, - poise_damage_increase: 0, - knockback: 3.5, - range: 4.5, - angle: 50.0, - base_buildup_duration: 0.2, - base_swing_duration: 0.1, - hit_timing: 0.5, - base_recover_duration: 0.45, - forward_movement: 0.0, - damage_kind: Crushing, - )], - initial_energy_gain: 5.0, - max_energy_gain: 15.0, - energy_increase: 5.0, - speed_increase: 0.1, - max_speed_increase: 0.4, - scales_from_combo: 2, - is_interruptible: false, - ori_modifier: 1.0, -) +// ComboMelee( +// stage_data: [( +// stage: 1, +// base_damage: 15.0, +// damage_increase: 1.0, +// base_poise_damage: 20, +// poise_damage_increase: 0, +// knockback: 3.5, +// range: 4.5, +// angle: 50.0, +// base_buildup_duration: 0.2, +// base_swing_duration: 0.1, +// hit_timing: 0.5, +// base_recover_duration: 0.45, +// forward_movement: 0.0, +// damage_kind: Crushing, +// )], +// initial_energy_gain: 5.0, +// max_energy_gain: 15.0, +// energy_increase: 5.0, +// speed_increase: 0.1, +// max_speed_increase: 0.4, +// scales_from_combo: 2, +// is_interruptible: false, +// ori_modifier: 1.0, +// ) +BasicMelee( + energy_cost: 0, + buildup_duration: 0.1, + swing_duration: 0.05, + recover_duration: 0.1, + base_damage: 10.0, + base_poise_damage: 0, + knockback: ( strength: 0.0, direction: Away), + range: 5.0, + max_angle: 45.0, + damage_effect: None, + damage_kind: Crushing, +) \ No newline at end of file diff --git a/assets/common/abilities/sword/triplestrike.ron b/assets/common/abilities/sword/triplestrike.ron index 84a55425d2..0a35bec8d2 100644 --- a/assets/common/abilities/sword/triplestrike.ron +++ b/assets/common/abilities/sword/triplestrike.ron @@ -1,78 +1,91 @@ -ComboMelee( - stage_data: [ - ( - stage: 1, - base_damage: 10.0, - damage_increase: 1.0, - base_poise_damage: 10, - poise_damage_increase: 0, - knockback: 0.0, - range: 4.0, - angle: 30.0, - base_buildup_duration: 0.1, - base_swing_duration: 0.075, - hit_timing: 0.5, - base_recover_duration: 0.15, - forward_movement: 0.5, - damage_kind: Slashing, - damage_effect: Some(Buff(( - kind: Bleeding, - dur_secs: 10.0, - strength: DamageFraction(0.1), - chance: 0.1, - ))), - ), - ( - stage: 2, - base_damage: 8.0, - damage_increase: 1.5, - base_poise_damage: 13, - poise_damage_increase: 0, - knockback: 2.0, - range: 3.5, - angle: 40.0, - base_buildup_duration: 0.1, - base_swing_duration: 0.1, - hit_timing: 0.5, - base_recover_duration: 0.3, - forward_movement: 0.0, - damage_kind: Slashing, - damage_effect: Some(Buff(( - kind: Bleeding, - dur_secs: 10.0, - strength: DamageFraction(0.1), - chance: 0.1, - ))), - ), - ( - stage: 3, - base_damage: 13, - damage_increase: 2, - base_poise_damage: 15, - poise_damage_increase: 0, - knockback: 2.0, - range: 6.0, - angle: 10.0, - base_buildup_duration: 0.15, - base_swing_duration: 0.1, - hit_timing: 0.2, - base_recover_duration: 0.35, - forward_movement: 1.2, - damage_kind: Piercing, - damage_effect: Some(Buff(( - kind: Bleeding, - dur_secs: 10.0, - strength: DamageFraction(0.1), - chance: 0.1, - ))), - ), - ], - initial_energy_gain: 0, - max_energy_gain: 20.0, - energy_increase: 2.5, - speed_increase: 0.1, - max_speed_increase: 0.8, - scales_from_combo: 2, - is_interruptible: true, - ori_modifier: 1.0, +// ComboMelee( +// stage_data: [ +// ( +// stage: 1, +// base_damage: 10.0, +// damage_increase: 1.0, +// base_poise_damage: 10, +// poise_damage_increase: 0, +// knockback: 0.0, +// range: 4.0, +// angle: 30.0, +// base_buildup_duration: 0.1, +// base_swing_duration: 0.075, +// hit_timing: 0.5, +// base_recover_duration: 0.15, +// forward_movement: 0.5, +// damage_kind: Slashing, +// damage_effect: Some(Buff(( +// kind: Bleeding, +// dur_secs: 10.0, +// strength: DamageFraction(0.1), +// chance: 0.1, +// ))), +// ), +// ( +// stage: 2, +// base_damage: 8.0, +// damage_increase: 1.5, +// base_poise_damage: 13, +// poise_damage_increase: 0, +// knockback: 2.0, +// range: 3.5, +// angle: 40.0, +// base_buildup_duration: 0.1, +// base_swing_duration: 0.1, +// hit_timing: 0.5, +// base_recover_duration: 0.3, +// forward_movement: 0.0, +// damage_kind: Slashing, +// damage_effect: Some(Buff(( +// kind: Bleeding, +// dur_secs: 10.0, +// strength: DamageFraction(0.1), +// chance: 0.1, +// ))), +// ), +// ( +// stage: 3, +// base_damage: 13, +// damage_increase: 2, +// base_poise_damage: 15, +// poise_damage_increase: 0, +// knockback: 2.0, +// range: 6.0, +// angle: 10.0, +// base_buildup_duration: 0.15, +// base_swing_duration: 0.1, +// hit_timing: 0.2, +// base_recover_duration: 0.35, +// forward_movement: 1.2, +// damage_kind: Piercing, +// damage_effect: Some(Buff(( +// kind: Bleeding, +// dur_secs: 10.0, +// strength: DamageFraction(0.1), +// chance: 0.1, +// ))), +// ), +// ], +// initial_energy_gain: 0, +// max_energy_gain: 20.0, +// energy_increase: 2.5, +// speed_increase: 0.1, +// max_speed_increase: 0.8, +// scales_from_combo: 2, +// is_interruptible: true, +// ori_modifier: 1.0, +// ) +BasicMelee( + energy_cost: 0, + buildup_duration: 0.1, + swing_duration: 0.05, + recover_duration: 0.1, + base_damage: 10.0, + base_poise_damage: 0, + knockback: ( strength: 0.0, direction: Away), + range: 5.0, + max_angle: 45.0, + damage_effect: None, + damage_kind: Piercing, ) diff --git a/common/src/combat.rs b/common/src/combat.rs index 51a12c133b..80ee62ac3a 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -12,7 +12,7 @@ use crate::{ }, skillset::SkillGroupKind, Alignment, Body, CharacterState, Combo, Energy, Health, HealthChange, Inventory, Ori, - Player, Poise, SkillSet, Stats, + Player, Poise, PoiseChange, SkillSet, Stats, }, event::ServerEvent, outcome::Outcome, @@ -70,6 +70,7 @@ pub struct TargetInfo<'a> { pub pos: Vec3, pub ori: Option<&'a Ori>, pub char_state: Option<&'a CharacterState>, + pub energy: Option<&'a Energy>, } #[derive(Clone, Copy)] @@ -135,12 +136,12 @@ impl Attack { target: &TargetInfo, source: AttackSource, dir: Dir, - kind: DamageKind, + damage: Damage, mut emit: impl FnMut(ServerEvent), mut emit_outcome: impl FnMut(Outcome), ) -> f32 { let damage_reduction = - Damage::compute_damage_reduction(target.inventory, target.stats, Some(kind)); + Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats); let block_reduction = match source { AttackSource::Melee => { if let (Some(CharacterState::BasicBlock(data)), Some(ori)) = @@ -223,7 +224,7 @@ impl Attack { &target, attack_source, dir, - damage.damage.kind, + damage.damage, &mut emit, &mut emit_outcome, ); @@ -243,6 +244,57 @@ impl Attack { entity: target.entity, change, }); + match damage.damage.kind { + DamageKind::Slashing => { + // For slashing damage, reduce target energy by some fraction of applied + // damage. When target would lose more energy than they have, deal an + // equivalent amount of damage + if let Some(target_energy) = target.energy { + let energy_change = applied_damage * SLASHING_ENERGY_FRACTION; + if energy_change > target_energy.current() { + let health_change = HealthChange { + amount: -(energy_change - target_energy.current()), + by: attacker.map(|x| x.into()), + cause: Some(damage.damage.source), + time, + }; + emit(ServerEvent::HealthChange { + entity: target.entity, + change: health_change, + }); + } + emit(ServerEvent::EnergyChange { + entity: target.entity, + change: -energy_change, + }); + } + }, + DamageKind::Crushing => { + // For crushing damage, reduce target poise by some fraction of the amount + // of damage that was reduced by target's protection + // Damage reduction should never equal 1 here as otherwise the check above + // that health change amount is greater than 0 would fail. + let reduced_damage = + applied_damage * damage_reduction / (1.0 - damage_reduction); + let poise = reduced_damage * CRUSHING_POISE_FRACTION; + let change = Poise::apply_poise_reduction(poise, target.inventory); + let poise_change = PoiseChange { + amount: change, + impulse: *dir, + by: attacker.map(|x| x.into()), + cause: Some(damage.damage.source), + }; + if change.abs() > Poise::POISE_EPSILON { + emit(ServerEvent::PoiseChange { + entity: target.entity, + change: poise_change, + }); + } + }, + // Piercing damage ignores some penetration, and is handled when damage + // reduction is computed Energy is a placeholder damage type + DamageKind::Piercing | DamageKind::Energy => {}, + } for effect in damage.effects.iter() { match effect { CombatEffect::Knockback(kb) => { @@ -297,10 +349,15 @@ impl Attack { let change = -Poise::apply_poise_reduction(*p, target.inventory) * strength_modifier; if change.abs() > Poise::POISE_EPSILON { + let poise_change = PoiseChange { + amount: change, + impulse: *dir, + by: attacker.map(|x| x.into()), + cause: Some(damage.damage.source), + }; emit(ServerEvent::PoiseChange { entity: target.entity, - change, - kb_dir: *dir, + change: poise_change, }); } }, @@ -434,10 +491,15 @@ impl Attack { let change = -Poise::apply_poise_reduction(p, target.inventory) * strength_modifier; if change.abs() > Poise::POISE_EPSILON { + let poise_change = PoiseChange { + amount: change, + impulse: *dir, + by: attacker.map(|x| x.into()), + cause: Some(attack_source.into()), + }; emit(ServerEvent::PoiseChange { entity: target.entity, - change, - kb_dir: *dir, + change: poise_change, }); } }, @@ -649,20 +711,37 @@ pub enum DamageSource { Other, } +impl From for DamageSource { + fn from(attack: AttackSource) -> Self { + match attack { + AttackSource::Melee => DamageSource::Melee, + AttackSource::Projectile => DamageSource::Projectile, + AttackSource::Explosion => DamageSource::Explosion, + AttackSource::Shockwave => DamageSource::Shockwave, + AttackSource::Beam => DamageSource::Energy, + } + } +} + /// DamageKind for the purpose of differentiating damage reduction #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] pub enum DamageKind { - /// Arrows/Sword dash + /// Bypasses some protection from armor Piercing, - /// Swords/axes + /// Reduces energy of target, dealing additional damage when target energy + /// is 0 Slashing, - /// Hammers + /// Deals additional poise damage the more armored the target is Crushing, - /// Staves/sceptres (TODO: differentiate further once there are more magic - /// weapons) + /// Catch all for remaining damage kinds (TODO: differentiate further with + /// staff/sceptre reworks Energy, } +const PIERCING_PENETRATION_FRACTION: f32 = 1.0; +const SLASHING_ENERGY_FRACTION: f32 = 1.0; +const CRUSHING_POISE_FRACTION: f32 = 1.0; + #[cfg(not(target_arch = "wasm32"))] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct Damage { @@ -675,9 +754,9 @@ pub struct Damage { impl Damage { /// Returns the total damage reduction provided by all equipped items pub fn compute_damage_reduction( + damage: Option, inventory: Option<&Inventory>, stats: Option<&Stats>, - kind: Option, ) -> f32 { let inventory_dr = if let Some(inventory) = inventory { let protection = inventory @@ -696,12 +775,17 @@ impl Damage { }) .sum::>(); - let kind_modifier = if matches!(kind, Some(DamageKind::Piercing)) { - 0.75 + let penetration = if let Some(damage) = damage { + if let DamageKind::Piercing = damage.kind { + damage.value * PIERCING_PENETRATION_FRACTION + } else { + 0.0 + } } else { - 1.0 + 0.0 }; - let protection = protection.map(|dr| dr * kind_modifier); + + let protection = protection.map(|p| p - penetration); const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0; @@ -971,7 +1055,7 @@ pub fn combat_rating( // Normalized with a standard max health of 100 let health_rating = health.base_max() / 100.0 - / (1.0 - Damage::compute_damage_reduction(Some(inventory), None, None)).max(0.00001); + / (1.0 - Damage::compute_damage_reduction(None, Some(inventory), None)).max(0.00001); // Normalized with a standard max energy of 100 and energy reward multiplier of // x1 diff --git a/common/src/comp/mod.rs b/common/src/comp/mod.rs index be2c405d12..9476eb598f 100644 --- a/common/src/comp/mod.rs +++ b/common/src/comp/mod.rs @@ -96,7 +96,7 @@ pub use self::{ }, player::DisconnectReason, player::{AliasError, Player, MAX_ALIAS_LEN}, - poise::{Poise, PoiseState}, + poise::{Poise, PoiseChange, PoiseState}, projectile::{Projectile, ProjectileConstructor}, shockwave::{Shockwave, ShockwaveHitEntities}, skillset::{ diff --git a/common/src/comp/poise.rs b/common/src/comp/poise.rs index 9f235e2564..184fab66f0 100644 --- a/common/src/comp/poise.rs +++ b/common/src/comp/poise.rs @@ -1,4 +1,5 @@ use crate::{ + combat::{DamageContributor, DamageSource}, comp::{ self, inventory::item::{armor::Protection, ItemKind}, @@ -13,6 +14,20 @@ use specs_idvs::IdvStorage; use std::{ops::Mul, time::Duration}; use vek::*; +#[derive(Clone, Copy, Debug, Serialize, Deserialize)] +pub struct PoiseChange { + /// The amount of the poise change + pub amount: f32, + /// The direction that the poise change came from, used for when the target + /// is knocked down + pub impulse: Vec3, + /// The the individual or group who caused the poise change (None if the + /// damage wasn't caused by an entity) + pub by: Option, + /// The category of action that resulted in the poise change + pub cause: Option, +} + #[derive(Clone, Copy, Debug, Serialize, Deserialize)] /// Poise is represented by u32s within the module, but treated as a float by /// the rest of the game. @@ -149,11 +164,11 @@ impl Poise { } } - pub fn change_by(&mut self, change: f32, impulse: Vec3) { - self.current = (((self.current() + change).clamp(0.0, f32::from(Self::MAX_POISE)) + pub fn change(&mut self, change: PoiseChange) { + self.current = (((self.current() + change.amount).clamp(0.0, f32::from(Self::MAX_POISE)) * Self::SCALING_FACTOR_FLOAT) as u32) .min(self.maximum); - self.last_change = Dir::from_unnormalized(impulse).unwrap_or_default(); + self.last_change = Dir::from_unnormalized(change.impulse).unwrap_or_default(); } pub fn reset(&mut self) { self.current = self.maximum; } diff --git a/common/src/event.rs b/common/src/event.rs index 0c551ed39d..d1e8215a11 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -53,8 +53,7 @@ pub enum ServerEvent { }, PoiseChange { entity: EcsEntity, - change: f32, - kb_dir: Vec3, + change: comp::PoiseChange, }, Delete(EcsEntity), Destroy { diff --git a/common/systems/src/beam.rs b/common/systems/src/beam.rs index cb5aabc8c9..1ff263796b 100644 --- a/common/systems/src/beam.rs +++ b/common/systems/src/beam.rs @@ -226,6 +226,7 @@ impl<'a> System<'a> for Sys { pos: pos_b.0, ori: read_data.orientations.get(target), char_state: read_data.character_states.get(target), + energy: read_data.energies.get(target), }; // PvP check diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index 559b6a5fea..4bd3516a7b 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -165,9 +165,9 @@ impl<'a> System<'a> for Sys { } let damage_reduction = Damage::compute_damage_reduction( + None, read_data.inventories.get(entity), Some(&stat), - None, ); if (damage_reduction - 1.0).abs() < f32::EPSILON { for (id, buff) in buff_comp.buffs.iter() { diff --git a/common/systems/src/melee.rs b/common/systems/src/melee.rs index d8f0a9ca9d..ebc5d750ae 100644 --- a/common/systems/src/melee.rs +++ b/common/systems/src/melee.rs @@ -164,6 +164,7 @@ impl<'a> System<'a> for Sys { pos: pos_b.0, ori: read_data.orientations.get(target), char_state: read_data.char_states.get(target), + energy: read_data.energies.get(target), }; // PvP check diff --git a/common/systems/src/projectile.rs b/common/systems/src/projectile.rs index 1f0013d669..3c3f204902 100644 --- a/common/systems/src/projectile.rs +++ b/common/systems/src/projectile.rs @@ -279,6 +279,7 @@ fn dispatch_hit( pos: target_pos, ori: projectile_target_info.ori, char_state: read_data.character_states.get(target), + energy: read_data.energies.get(target), }; // TODO: Is it possible to have projectile without body?? diff --git a/common/systems/src/shockwave.rs b/common/systems/src/shockwave.rs index 2074d74aa7..259756ea72 100644 --- a/common/systems/src/shockwave.rs +++ b/common/systems/src/shockwave.rs @@ -207,6 +207,7 @@ impl<'a> System<'a> for Sys { pos: pos_b.0, ori: read_data.orientations.get(target), char_state: read_data.character_states.get(target), + energy: read_data.energies.get(target), }; // PvP check diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index 2b38d30698..5097710ad0 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -3,8 +3,8 @@ use common::{ comp::{ self, skills::{GeneralSkill, Skill}, - Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Pos, SkillSet, Stats, - StatsModifier, + Body, CharacterState, Combo, Energy, Health, Inventory, Poise, PoiseChange, Pos, SkillSet, + Stats, StatsModifier, }, event::{EventBus, ServerEvent}, resources::{DeltaTime, EntitiesDiedLastTick, Time}, @@ -173,7 +173,13 @@ impl<'a> System<'a> for Sys { if res_poise { let poise = &mut *poise; - poise.change_by(poise.regen_rate * dt, Vec3::zero()); + let poise_change = PoiseChange { + amount: poise.regen_rate * dt, + impulse: Vec3::zero(), + by: None, + cause: None, + }; + poise.change(poise_change); poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0); } }, diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index 67b4c2ce4c..fc57857cf1 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -48,14 +48,29 @@ enum DamageContrib { NotFound, } -pub fn handle_poise(server: &Server, entity: EcsEntity, change: f32, knockback_dir: Vec3) { +pub fn handle_poise(server: &Server, entity: EcsEntity, change: comp::PoiseChange) { let ecs = &server.state.ecs(); if let Some(character_state) = ecs.read_storage::().get(entity) { - // Entity is invincible to poise change during stunned/staggered character state + // Entity is invincible to poise change during stunned/staggered character + // state, but the mitigated poise damage is converted to health damage instead if !character_state.is_stunned() { if let Some(mut poise) = ecs.write_storage::().get_mut(entity) { - poise.change_by(change, knockback_dir); + poise.change(change); } + } else { + // TODO: Look into multiplying health by some fraction dependent on poise state + let time = ecs.read_resource::