diff --git a/CHANGELOG.md b/CHANGELOG.md index 5ec9bb5b61..777e49dd91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Rivers now make ambient sounds (again) - Added a setting to see own speech bubbles - Added an option to allow players to remove keybindings +- Piercing damage now ignores an amount of protection equal to damage value +- Slashing damage now reduces target's energy by an amount equal to damage dealt to target post-mitigation +- Crushing damage now does poise damage to a target equal to the amount mitigated by armor ### Changed @@ -54,6 +57,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Yeti loot table modified - Phoenix feathers are now Legendary quality - Green/Red lantern now shine their respective color instead of the default lantern color +- Poise damage dealt to a target that is in a stunned state is now converted to health damage at an efficiency dependent on the severity of the stunned state +- You are now immune to poise damage for 1 second after leaving a stunned state +- Removed or reduced poise damage from most abilities ### Removed diff --git a/assets/common/abilities/axe/doublestrike.ron b/assets/common/abilities/axe/doublestrike.ron index 59172a0787..20b04e42f3 100644 --- a/assets/common/abilities/axe/doublestrike.ron +++ b/assets/common/abilities/axe/doublestrike.ron @@ -3,8 +3,8 @@ ComboMelee( ( stage: 1, base_damage: 11.0, - base_poise_damage: 12, damage_increase: 1.0, + base_poise_damage: 0, poise_damage_increase: 0, knockback: 5.0, range: 3.5, @@ -25,8 +25,8 @@ ComboMelee( ( stage: 2, base_damage: 13.0, - base_poise_damage: 20, damage_increase: 1.5, + base_poise_damage: 0, poise_damage_increase: 0, knockback: 6.0, range: 3.5, @@ -53,4 +53,4 @@ ComboMelee( scales_from_combo: 2, is_interruptible: false, ori_modifier: 1.0, -) +) \ No newline at end of file diff --git a/assets/common/abilities/axe/leap.ron b/assets/common/abilities/axe/leap.ron index a236900af2..aa80887159 100644 --- a/assets/common/abilities/axe/leap.ron +++ b/assets/common/abilities/axe/leap.ron @@ -4,8 +4,8 @@ LeapMelee( movement_duration: 0.2, swing_duration: 0.2, recover_duration: 0.2, - base_damage: 15.0, - base_poise_damage: 70, + base_damage: 30.0, + base_poise_damage: 0, knockback: 12.0, range: 4.5, max_angle: 30.0, diff --git a/assets/common/abilities/axe/spin.ron b/assets/common/abilities/axe/spin.ron index bc1628de4d..138ca9cefc 100644 --- a/assets/common/abilities/axe/spin.ron +++ b/assets/common/abilities/axe/spin.ron @@ -2,8 +2,8 @@ SpinMelee( buildup_duration: 0.2, swing_duration: 0.6, recover_duration: 0.2, - base_damage: 7.0, - base_poise_damage: 25, + base_damage: 8.0, + base_poise_damage: 10, knockback: ( strength: 0.0, direction: Away), range: 3.5, damage_effect: Some(Buff(( diff --git a/assets/common/abilities/bow/charged.ron b/assets/common/abilities/bow/charged.ron index 4cac8d91fc..43393bb32a 100644 --- a/assets/common/abilities/bow/charged.ron +++ b/assets/common/abilities/bow/charged.ron @@ -4,7 +4,7 @@ ChargedRanged( initial_regen: 0.5, scaled_regen: 12.0, initial_damage: 0.5, - scaled_damage: 14.0, + scaled_damage: 12.0, initial_knockback: 0.0, scaled_knockback: 10.0, buildup_duration: 0.2, diff --git a/assets/common/abilities/hammer/charged.ron b/assets/common/abilities/hammer/charged.ron index 9d8a737382..3b9eb35e88 100644 --- a/assets/common/abilities/hammer/charged.ron +++ b/assets/common/abilities/hammer/charged.ron @@ -1,10 +1,10 @@ ChargedMelee( energy_cost: 1, energy_drain: 30.0, - initial_damage: 1.0, - scaled_damage: 16.0, - initial_poise_damage: 5, - scaled_poise_damage: 75, + initial_damage: 0.0, + scaled_damage: 20.0, + initial_poise_damage: 0, + scaled_poise_damage: 30, initial_knockback: 5.0, scaled_knockback: 20.0, range: 3.5, diff --git a/assets/common/abilities/hammer/leap.ron b/assets/common/abilities/hammer/leap.ron index f36ca54f1d..1b1f1cd21c 100644 --- a/assets/common/abilities/hammer/leap.ron +++ b/assets/common/abilities/hammer/leap.ron @@ -4,8 +4,8 @@ LeapMelee( movement_duration: 0.8, swing_duration: 0.15, recover_duration: 0.2, - base_damage: 16.0, - base_poise_damage: 80, + base_damage: 25.0, + base_poise_damage: 40, knockback: 25.0, range: 4.5, max_angle: 360.0, diff --git a/assets/common/abilities/hammer/singlestrike.ron b/assets/common/abilities/hammer/singlestrike.ron index b5b0d54b84..625b220624 100644 --- a/assets/common/abilities/hammer/singlestrike.ron +++ b/assets/common/abilities/hammer/singlestrike.ron @@ -3,7 +3,7 @@ ComboMelee( stage: 1, base_damage: 15.0, damage_increase: 1.0, - base_poise_damage: 20, + base_poise_damage: 0, poise_damage_increase: 0, knockback: 3.5, range: 4.5, @@ -23,4 +23,4 @@ ComboMelee( scales_from_combo: 2, is_interruptible: false, ori_modifier: 1.0, -) +) \ No newline at end of file diff --git a/assets/common/abilities/sword/dash.ron b/assets/common/abilities/sword/dash.ron index ac9b370d02..07cfe5d615 100644 --- a/assets/common/abilities/sword/dash.ron +++ b/assets/common/abilities/sword/dash.ron @@ -3,7 +3,7 @@ DashMelee( base_damage: 8.0, scaled_damage: 16.0, base_poise_damage: 0, - scaled_poise_damage: 60, + scaled_poise_damage: 0, base_knockback: 8.0, scaled_knockback: 7.0, range: 4.0, diff --git a/assets/common/abilities/sword/spin.ron b/assets/common/abilities/sword/spin.ron index e0d146db98..f31969b279 100644 --- a/assets/common/abilities/sword/spin.ron +++ b/assets/common/abilities/sword/spin.ron @@ -2,8 +2,8 @@ SpinMelee( buildup_duration: 0.35, swing_duration: 0.4, recover_duration: 0.5, - base_damage: 13.0, - base_poise_damage: 13, + base_damage: 12.0, + base_poise_damage: 10, knockback: ( strength: 10.0, direction: Away), range: 3.5, damage_effect: Some(Buff(( diff --git a/assets/common/abilities/sword/triplestrike.ron b/assets/common/abilities/sword/triplestrike.ron index 84a55425d2..52dcf61e72 100644 --- a/assets/common/abilities/sword/triplestrike.ron +++ b/assets/common/abilities/sword/triplestrike.ron @@ -4,7 +4,7 @@ ComboMelee( stage: 1, base_damage: 10.0, damage_increase: 1.0, - base_poise_damage: 10, + base_poise_damage: 0, poise_damage_increase: 0, knockback: 0.0, range: 4.0, @@ -26,7 +26,7 @@ ComboMelee( stage: 2, base_damage: 8.0, damage_increase: 1.5, - base_poise_damage: 13, + base_poise_damage: 0, poise_damage_increase: 0, knockback: 2.0, range: 3.5, @@ -46,9 +46,9 @@ ComboMelee( ), ( stage: 3, - base_damage: 13, + base_damage: 10.0, damage_increase: 2, - base_poise_damage: 15, + base_poise_damage: 0, poise_damage_increase: 0, knockback: 2.0, range: 6.0, @@ -75,4 +75,4 @@ ComboMelee( scales_from_combo: 2, is_interruptible: true, ori_modifier: 1.0, -) +) \ No newline at end of file diff --git a/common/Cargo.toml b/common/Cargo.toml index 095d169e7f..e0c023cb93 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -88,6 +88,7 @@ criterion = "0.3" #test tracing-subscriber = { version = "0.3.2", default-features = false, features = ["fmt", "time", "ansi", "smallvec", "env-filter"] } +petgraph = "0.5.1" [[bench]] name = "chonk_benchmark" diff --git a/common/src/combat.rs b/common/src/combat.rs index 51a12c133b..5545668770 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,58 @@ 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), + time, + }; + 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 +350,16 @@ 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), + time, + }; emit(ServerEvent::PoiseChange { entity: target.entity, - change, - kb_dir: *dir, + change: poise_change, }); } }, @@ -434,10 +493,16 @@ 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()), + time, + }; emit(ServerEvent::PoiseChange { entity: target.entity, - change, - kb_dir: *dir, + change: poise_change, }); } }, @@ -649,20 +714,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 +757,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 +778,19 @@ 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) + .min(protection.unwrap_or(0.0)) + .max(0.0) + } 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 +1060,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/health.rs b/common/src/comp/health.rs index e96abdeb6e..44bbb2017e 100644 --- a/common/src/comp/health.rs +++ b/common/src/comp/health.rs @@ -17,7 +17,7 @@ use std::ops::Mul; pub struct HealthChange { /// The amount of the health change, negative is damage, positive is healing pub amount: f32, - /// The the individual or group who caused the health change (None if the + /// The individual or group who caused the health change (None if the /// damage wasn't caused by an entity) pub by: Option, /// The category of action that resulted in the health change 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..916844e44e 100644 --- a/common/src/comp/poise.rs +++ b/common/src/comp/poise.rs @@ -1,9 +1,11 @@ use crate::{ + combat::{DamageContributor, DamageSource}, comp::{ self, inventory::item::{armor::Protection, ItemKind}, CharacterState, Inventory, }, + resources::Time, states, util::Dir, }; @@ -13,6 +15,22 @@ 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 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, + /// The time that the poise change occurred at + pub time: Time, +} + #[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. @@ -36,6 +54,8 @@ pub struct Poise { pub last_change: Dir, /// Rate of poise regeneration per tick. Starts at zero and accelerates. pub regen_rate: f32, + /// Time that entity was last in a poise state + last_stun_time: Option