Merge branch 'sam/damage-types' into 'master'

Physical damage types now have effects

See merge request veloren/veloren!3087
This commit is contained in:
Samuel Keiffer
2022-01-13 03:55:44 +00:00
27 changed files with 293 additions and 102 deletions

View File

@ -31,6 +31,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Rivers now make ambient sounds (again) - Rivers now make ambient sounds (again)
- Added a setting to see own speech bubbles - Added a setting to see own speech bubbles
- Added an option to allow players to remove keybindings - 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 ### Changed
@ -54,6 +57,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Yeti loot table modified - Yeti loot table modified
- Phoenix feathers are now Legendary quality - Phoenix feathers are now Legendary quality
- Green/Red lantern now shine their respective color instead of the default lantern color - 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 ### Removed

View File

@ -3,8 +3,8 @@ ComboMelee(
( (
stage: 1, stage: 1,
base_damage: 11.0, base_damage: 11.0,
base_poise_damage: 12,
damage_increase: 1.0, damage_increase: 1.0,
base_poise_damage: 0,
poise_damage_increase: 0, poise_damage_increase: 0,
knockback: 5.0, knockback: 5.0,
range: 3.5, range: 3.5,
@ -25,8 +25,8 @@ ComboMelee(
( (
stage: 2, stage: 2,
base_damage: 13.0, base_damage: 13.0,
base_poise_damage: 20,
damage_increase: 1.5, damage_increase: 1.5,
base_poise_damage: 0,
poise_damage_increase: 0, poise_damage_increase: 0,
knockback: 6.0, knockback: 6.0,
range: 3.5, range: 3.5,
@ -53,4 +53,4 @@ ComboMelee(
scales_from_combo: 2, scales_from_combo: 2,
is_interruptible: false, is_interruptible: false,
ori_modifier: 1.0, ori_modifier: 1.0,
) )

View File

@ -4,8 +4,8 @@ LeapMelee(
movement_duration: 0.2, movement_duration: 0.2,
swing_duration: 0.2, swing_duration: 0.2,
recover_duration: 0.2, recover_duration: 0.2,
base_damage: 15.0, base_damage: 30.0,
base_poise_damage: 70, base_poise_damage: 0,
knockback: 12.0, knockback: 12.0,
range: 4.5, range: 4.5,
max_angle: 30.0, max_angle: 30.0,

View File

@ -2,8 +2,8 @@ SpinMelee(
buildup_duration: 0.2, buildup_duration: 0.2,
swing_duration: 0.6, swing_duration: 0.6,
recover_duration: 0.2, recover_duration: 0.2,
base_damage: 7.0, base_damage: 8.0,
base_poise_damage: 25, base_poise_damage: 10,
knockback: ( strength: 0.0, direction: Away), knockback: ( strength: 0.0, direction: Away),
range: 3.5, range: 3.5,
damage_effect: Some(Buff(( damage_effect: Some(Buff((

View File

@ -4,7 +4,7 @@ ChargedRanged(
initial_regen: 0.5, initial_regen: 0.5,
scaled_regen: 12.0, scaled_regen: 12.0,
initial_damage: 0.5, initial_damage: 0.5,
scaled_damage: 14.0, scaled_damage: 12.0,
initial_knockback: 0.0, initial_knockback: 0.0,
scaled_knockback: 10.0, scaled_knockback: 10.0,
buildup_duration: 0.2, buildup_duration: 0.2,

View File

@ -1,10 +1,10 @@
ChargedMelee( ChargedMelee(
energy_cost: 1, energy_cost: 1,
energy_drain: 30.0, energy_drain: 30.0,
initial_damage: 1.0, initial_damage: 0.0,
scaled_damage: 16.0, scaled_damage: 20.0,
initial_poise_damage: 5, initial_poise_damage: 0,
scaled_poise_damage: 75, scaled_poise_damage: 30,
initial_knockback: 5.0, initial_knockback: 5.0,
scaled_knockback: 20.0, scaled_knockback: 20.0,
range: 3.5, range: 3.5,

View File

@ -4,8 +4,8 @@ LeapMelee(
movement_duration: 0.8, movement_duration: 0.8,
swing_duration: 0.15, swing_duration: 0.15,
recover_duration: 0.2, recover_duration: 0.2,
base_damage: 16.0, base_damage: 25.0,
base_poise_damage: 80, base_poise_damage: 40,
knockback: 25.0, knockback: 25.0,
range: 4.5, range: 4.5,
max_angle: 360.0, max_angle: 360.0,

View File

@ -3,7 +3,7 @@ ComboMelee(
stage: 1, stage: 1,
base_damage: 15.0, base_damage: 15.0,
damage_increase: 1.0, damage_increase: 1.0,
base_poise_damage: 20, base_poise_damage: 0,
poise_damage_increase: 0, poise_damage_increase: 0,
knockback: 3.5, knockback: 3.5,
range: 4.5, range: 4.5,
@ -23,4 +23,4 @@ ComboMelee(
scales_from_combo: 2, scales_from_combo: 2,
is_interruptible: false, is_interruptible: false,
ori_modifier: 1.0, ori_modifier: 1.0,
) )

View File

@ -3,7 +3,7 @@ DashMelee(
base_damage: 8.0, base_damage: 8.0,
scaled_damage: 16.0, scaled_damage: 16.0,
base_poise_damage: 0, base_poise_damage: 0,
scaled_poise_damage: 60, scaled_poise_damage: 0,
base_knockback: 8.0, base_knockback: 8.0,
scaled_knockback: 7.0, scaled_knockback: 7.0,
range: 4.0, range: 4.0,

View File

@ -2,8 +2,8 @@ SpinMelee(
buildup_duration: 0.35, buildup_duration: 0.35,
swing_duration: 0.4, swing_duration: 0.4,
recover_duration: 0.5, recover_duration: 0.5,
base_damage: 13.0, base_damage: 12.0,
base_poise_damage: 13, base_poise_damage: 10,
knockback: ( strength: 10.0, direction: Away), knockback: ( strength: 10.0, direction: Away),
range: 3.5, range: 3.5,
damage_effect: Some(Buff(( damage_effect: Some(Buff((

View File

@ -4,7 +4,7 @@ ComboMelee(
stage: 1, stage: 1,
base_damage: 10.0, base_damage: 10.0,
damage_increase: 1.0, damage_increase: 1.0,
base_poise_damage: 10, base_poise_damage: 0,
poise_damage_increase: 0, poise_damage_increase: 0,
knockback: 0.0, knockback: 0.0,
range: 4.0, range: 4.0,
@ -26,7 +26,7 @@ ComboMelee(
stage: 2, stage: 2,
base_damage: 8.0, base_damage: 8.0,
damage_increase: 1.5, damage_increase: 1.5,
base_poise_damage: 13, base_poise_damage: 0,
poise_damage_increase: 0, poise_damage_increase: 0,
knockback: 2.0, knockback: 2.0,
range: 3.5, range: 3.5,
@ -46,9 +46,9 @@ ComboMelee(
), ),
( (
stage: 3, stage: 3,
base_damage: 13, base_damage: 10.0,
damage_increase: 2, damage_increase: 2,
base_poise_damage: 15, base_poise_damage: 0,
poise_damage_increase: 0, poise_damage_increase: 0,
knockback: 2.0, knockback: 2.0,
range: 6.0, range: 6.0,
@ -75,4 +75,4 @@ ComboMelee(
scales_from_combo: 2, scales_from_combo: 2,
is_interruptible: true, is_interruptible: true,
ori_modifier: 1.0, ori_modifier: 1.0,
) )

View File

@ -12,7 +12,7 @@ use crate::{
}, },
skillset::SkillGroupKind, skillset::SkillGroupKind,
Alignment, Body, CharacterState, Combo, Energy, Health, HealthChange, Inventory, Ori, Alignment, Body, CharacterState, Combo, Energy, Health, HealthChange, Inventory, Ori,
Player, Poise, SkillSet, Stats, Player, Poise, PoiseChange, SkillSet, Stats,
}, },
event::ServerEvent, event::ServerEvent,
outcome::Outcome, outcome::Outcome,
@ -70,6 +70,7 @@ pub struct TargetInfo<'a> {
pub pos: Vec3<f32>, pub pos: Vec3<f32>,
pub ori: Option<&'a Ori>, pub ori: Option<&'a Ori>,
pub char_state: Option<&'a CharacterState>, pub char_state: Option<&'a CharacterState>,
pub energy: Option<&'a Energy>,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy)]
@ -135,12 +136,12 @@ impl Attack {
target: &TargetInfo, target: &TargetInfo,
source: AttackSource, source: AttackSource,
dir: Dir, dir: Dir,
kind: DamageKind, damage: Damage,
mut emit: impl FnMut(ServerEvent), mut emit: impl FnMut(ServerEvent),
mut emit_outcome: impl FnMut(Outcome), mut emit_outcome: impl FnMut(Outcome),
) -> f32 { ) -> f32 {
let damage_reduction = 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 { let block_reduction = match source {
AttackSource::Melee => { AttackSource::Melee => {
if let (Some(CharacterState::BasicBlock(data)), Some(ori)) = if let (Some(CharacterState::BasicBlock(data)), Some(ori)) =
@ -223,7 +224,7 @@ impl Attack {
&target, &target,
attack_source, attack_source,
dir, dir,
damage.damage.kind, damage.damage,
&mut emit, &mut emit,
&mut emit_outcome, &mut emit_outcome,
); );
@ -243,6 +244,58 @@ impl Attack {
entity: target.entity, entity: target.entity,
change, 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() { for effect in damage.effects.iter() {
match effect { match effect {
CombatEffect::Knockback(kb) => { CombatEffect::Knockback(kb) => {
@ -297,10 +350,16 @@ impl Attack {
let change = -Poise::apply_poise_reduction(*p, target.inventory) let change = -Poise::apply_poise_reduction(*p, target.inventory)
* strength_modifier; * strength_modifier;
if change.abs() > Poise::POISE_EPSILON { 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 { emit(ServerEvent::PoiseChange {
entity: target.entity, entity: target.entity,
change, change: poise_change,
kb_dir: *dir,
}); });
} }
}, },
@ -434,10 +493,16 @@ impl Attack {
let change = let change =
-Poise::apply_poise_reduction(p, target.inventory) * strength_modifier; -Poise::apply_poise_reduction(p, target.inventory) * strength_modifier;
if change.abs() > Poise::POISE_EPSILON { 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 { emit(ServerEvent::PoiseChange {
entity: target.entity, entity: target.entity,
change, change: poise_change,
kb_dir: *dir,
}); });
} }
}, },
@ -649,20 +714,37 @@ pub enum DamageSource {
Other, Other,
} }
impl From<AttackSource> 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 /// DamageKind for the purpose of differentiating damage reduction
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, Serialize, Deserialize)]
pub enum DamageKind { pub enum DamageKind {
/// Arrows/Sword dash /// Bypasses some protection from armor
Piercing, Piercing,
/// Swords/axes /// Reduces energy of target, dealing additional damage when target energy
/// is 0
Slashing, Slashing,
/// Hammers /// Deals additional poise damage the more armored the target is
Crushing, Crushing,
/// Staves/sceptres (TODO: differentiate further once there are more magic /// Catch all for remaining damage kinds (TODO: differentiate further with
/// weapons) /// staff/sceptre reworks
Energy, 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"))] #[cfg(not(target_arch = "wasm32"))]
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Damage { pub struct Damage {
@ -675,9 +757,9 @@ pub struct Damage {
impl Damage { impl Damage {
/// Returns the total damage reduction provided by all equipped items /// Returns the total damage reduction provided by all equipped items
pub fn compute_damage_reduction( pub fn compute_damage_reduction(
damage: Option<Self>,
inventory: Option<&Inventory>, inventory: Option<&Inventory>,
stats: Option<&Stats>, stats: Option<&Stats>,
kind: Option<DamageKind>,
) -> f32 { ) -> f32 {
let inventory_dr = if let Some(inventory) = inventory { let inventory_dr = if let Some(inventory) = inventory {
let protection = inventory let protection = inventory
@ -696,12 +778,19 @@ impl Damage {
}) })
.sum::<Option<f32>>(); .sum::<Option<f32>>();
let kind_modifier = if matches!(kind, Some(DamageKind::Piercing)) { let penetration = if let Some(damage) = damage {
0.75 if let DamageKind::Piercing = damage.kind {
(damage.value * PIERCING_PENETRATION_FRACTION)
.min(protection.unwrap_or(0.0))
.max(0.0)
} else {
0.0
}
} else { } 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; const FIFTY_PERCENT_DR_THRESHOLD: f32 = 60.0;
@ -971,7 +1060,7 @@ pub fn combat_rating(
// Normalized with a standard max health of 100 // Normalized with a standard max health of 100
let health_rating = health.base_max() let health_rating = health.base_max()
/ 100.0 / 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 // Normalized with a standard max energy of 100 and energy reward multiplier of
// x1 // x1

View File

@ -17,7 +17,7 @@ use std::ops::Mul;
pub struct HealthChange { pub struct HealthChange {
/// The amount of the health change, negative is damage, positive is healing /// The amount of the health change, negative is damage, positive is healing
pub amount: f32, 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) /// damage wasn't caused by an entity)
pub by: Option<DamageContributor>, pub by: Option<DamageContributor>,
/// The category of action that resulted in the health change /// The category of action that resulted in the health change

View File

@ -96,7 +96,7 @@ pub use self::{
}, },
player::DisconnectReason, player::DisconnectReason,
player::{AliasError, Player, MAX_ALIAS_LEN}, player::{AliasError, Player, MAX_ALIAS_LEN},
poise::{Poise, PoiseState}, poise::{Poise, PoiseChange, PoiseState},
projectile::{Projectile, ProjectileConstructor}, projectile::{Projectile, ProjectileConstructor},
shockwave::{Shockwave, ShockwaveHitEntities}, shockwave::{Shockwave, ShockwaveHitEntities},
skillset::{ skillset::{

View File

@ -1,9 +1,11 @@
use crate::{ use crate::{
combat::{DamageContributor, DamageSource},
comp::{ comp::{
self, self,
inventory::item::{armor::Protection, ItemKind}, inventory::item::{armor::Protection, ItemKind},
CharacterState, Inventory, CharacterState, Inventory,
}, },
resources::Time,
states, states,
util::Dir, util::Dir,
}; };
@ -13,6 +15,22 @@ use specs_idvs::IdvStorage;
use std::{ops::Mul, time::Duration}; use std::{ops::Mul, time::Duration};
use vek::*; 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<f32>,
/// The individual or group who caused the poise change (None if the
/// damage wasn't caused by an entity)
pub by: Option<DamageContributor>,
/// The category of action that resulted in the poise change
pub cause: Option<DamageSource>,
/// The time that the poise change occurred at
pub time: Time,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)] #[derive(Clone, Copy, Debug, Serialize, Deserialize)]
/// Poise is represented by u32s within the module, but treated as a float by /// Poise is represented by u32s within the module, but treated as a float by
/// the rest of the game. /// the rest of the game.
@ -36,6 +54,8 @@ pub struct Poise {
pub last_change: Dir, pub last_change: Dir,
/// Rate of poise regeneration per tick. Starts at zero and accelerates. /// Rate of poise regeneration per tick. Starts at zero and accelerates.
pub regen_rate: f32, pub regen_rate: f32,
/// Time that entity was last in a poise state
last_stun_time: Option<Time>,
} }
/// States to define effects of a poise change /// States to define effects of a poise change
@ -54,7 +74,9 @@ pub enum PoiseState {
} }
impl PoiseState { impl PoiseState {
pub fn poise_effect(&self, was_wielded: bool) -> (Option<CharacterState>, Option<f32>) { /// Returns the optional stunned character state and duration of stun, and
/// optional impulse strength corresponding to a particular poise state
pub fn poise_effect(&self, was_wielded: bool) -> (Option<(CharacterState, f64)>, Option<f32>) {
use states::{ use states::{
stunned::{Data, StaticData}, stunned::{Data, StaticData},
utils::StageSection, utils::StageSection,
@ -64,39 +86,55 @@ impl PoiseState {
let (charstate_parameters, impulse) = match self { let (charstate_parameters, impulse) = match self {
PoiseState::Normal => (None, None), PoiseState::Normal => (None, None),
PoiseState::Interrupted => ( PoiseState::Interrupted => (
Some((Duration::from_millis(125), Duration::from_millis(125), 0.80)), Some((Duration::from_millis(200), Duration::from_millis(200), 0.8)),
None, None,
), ),
PoiseState::Stunned => ( PoiseState::Stunned => (
Some((Duration::from_millis(300), Duration::from_millis(300), 0.65)), Some((Duration::from_millis(400), Duration::from_millis(400), 0.5)),
Some(5.0), None,
), ),
PoiseState::Dazed => ( PoiseState::Dazed => (
Some((Duration::from_millis(600), Duration::from_millis(250), 0.45)), Some((Duration::from_millis(750), Duration::from_millis(450), 0.2)),
Some(10.0), None,
), ),
PoiseState::KnockedDown => ( PoiseState::KnockedDown => (
Some((Duration::from_millis(750), Duration::from_millis(500), 0.4)), Some((Duration::from_millis(1000), Duration::from_millis(600), 0.0)),
Some(10.0), Some(10.0),
), ),
}; };
( (
charstate_parameters.map(|(buildup_duration, recover_duration, movement_speed)| { charstate_parameters.map(|(buildup_duration, recover_duration, movement_speed)| {
CharacterState::Stunned(Data { (
static_data: StaticData { CharacterState::Stunned(Data {
buildup_duration, static_data: StaticData {
recover_duration, buildup_duration,
movement_speed, recover_duration,
poise_state: *self, movement_speed,
}, poise_state: *self,
timer: Duration::default(), },
stage_section: StageSection::Buildup, timer: Duration::default(),
was_wielded, stage_section: StageSection::Buildup,
}) was_wielded,
}),
buildup_duration.as_secs_f64() + recover_duration.as_secs_f64(),
)
}), }),
impulse, impulse,
) )
} }
/// Returns the multiplier on poise damage to health damage for when the
/// target is in a poise state
pub fn damage_multiplier(&self) -> f32 {
match self {
Self::Interrupted => 0.1,
Self::Stunned => 0.25,
Self::Dazed => 0.5,
Self::KnockedDown => 1.0,
// Should never be reached
Self::Normal => 0.0,
}
}
} }
impl Poise { impl Poise {
@ -107,6 +145,9 @@ impl Poise {
/// can fit into an f32 with no loss to precision /// can fit into an f32 with no loss to precision
// Cast to u32 done as u32::from cannot be called inside constant // Cast to u32 done as u32::from cannot be called inside constant
const MAX_SCALED_POISE: u32 = Self::MAX_POISE as u32 * Self::SCALING_FACTOR_INT; const MAX_SCALED_POISE: u32 = Self::MAX_POISE as u32 * Self::SCALING_FACTOR_INT;
/// The amount of time after being in a poise state before you can take
/// poise damage again
const POISE_BUFFER_TIME: f64 = 1.0;
/// Used when comparisons to poise are needed outside this module. /// Used when comparisons to poise are needed outside this module.
// This value is chosen as anything smaller than this is more precise than our // This value is chosen as anything smaller than this is more precise than our
// units of poise. // units of poise.
@ -146,17 +187,27 @@ impl Poise {
maximum: poise, maximum: poise,
last_change: Dir::default(), last_change: Dir::default(),
regen_rate: 0.0, regen_rate: 0.0,
last_stun_time: None,
} }
} }
pub fn change_by(&mut self, change: f32, impulse: Vec3<f32>) { pub fn change(&mut self, change: PoiseChange) {
self.current = (((self.current() + change).clamp(0.0, f32::from(Self::MAX_POISE)) match self.last_stun_time {
* Self::SCALING_FACTOR_FLOAT) as u32) Some(last_time) if last_time.0 + Poise::POISE_BUFFER_TIME > change.time.0 => {},
.min(self.maximum); _ => {
self.last_change = Dir::from_unnormalized(impulse).unwrap_or_default(); 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(change.impulse).unwrap_or_default();
},
}
} }
pub fn reset(&mut self) { self.current = self.maximum; } pub fn reset(&mut self, time: Time, poise_state_time: f64) {
self.current = self.maximum;
self.last_stun_time = Some(Time(time.0 + poise_state_time));
}
/// Returns knockback as a Dir /// Returns knockback as a Dir
/// Kept as helper function should additional fields ever be added to last /// Kept as helper function should additional fields ever be added to last
@ -166,10 +217,10 @@ impl Poise {
/// Defines the poise states based on current poise value /// Defines the poise states based on current poise value
pub fn poise_state(&self) -> PoiseState { pub fn poise_state(&self) -> PoiseState {
match self.current() { match self.current() {
x if x > 70.0 => PoiseState::Normal, x if x > 50.0 => PoiseState::Normal,
x if x > 50.0 => PoiseState::Interrupted, x if x > 30.0 => PoiseState::Interrupted,
x if x > 40.0 => PoiseState::Stunned, x if x > 15.0 => PoiseState::Stunned,
x if x > 20.0 => PoiseState::Dazed, x if x > 5.0 => PoiseState::Dazed,
_ => PoiseState::KnockedDown, _ => PoiseState::KnockedDown,
} }
} }

View File

@ -53,8 +53,7 @@ pub enum ServerEvent {
}, },
PoiseChange { PoiseChange {
entity: EcsEntity, entity: EcsEntity,
change: f32, change: comp::PoiseChange,
kb_dir: Vec3<f32>,
}, },
Delete(EcsEntity), Delete(EcsEntity),
Destroy { Destroy {

View File

@ -226,6 +226,7 @@ impl<'a> System<'a> for Sys {
pos: pos_b.0, pos: pos_b.0,
ori: read_data.orientations.get(target), ori: read_data.orientations.get(target),
char_state: read_data.character_states.get(target), char_state: read_data.character_states.get(target),
energy: read_data.energies.get(target),
}; };
// PvP check // PvP check

View File

@ -165,9 +165,9 @@ impl<'a> System<'a> for Sys {
} }
let damage_reduction = Damage::compute_damage_reduction( let damage_reduction = Damage::compute_damage_reduction(
None,
read_data.inventories.get(entity), read_data.inventories.get(entity),
Some(&stat), Some(&stat),
None,
); );
if (damage_reduction - 1.0).abs() < f32::EPSILON { if (damage_reduction - 1.0).abs() < f32::EPSILON {
for (id, buff) in buff_comp.buffs.iter() { for (id, buff) in buff_comp.buffs.iter() {

View File

@ -12,7 +12,7 @@ use common::{
}, },
event::{EventBus, LocalEvent, ServerEvent}, event::{EventBus, LocalEvent, ServerEvent},
outcome::Outcome, outcome::Outcome,
resources::DeltaTime, resources::{DeltaTime, Time},
states::{ states::{
behavior::{JoinData, JoinStruct}, behavior::{JoinData, JoinStruct},
idle, idle,
@ -28,6 +28,7 @@ pub struct ReadData<'a> {
server_bus: Read<'a, EventBus<ServerEvent>>, server_bus: Read<'a, EventBus<ServerEvent>>,
local_bus: Read<'a, EventBus<LocalEvent>>, local_bus: Read<'a, EventBus<LocalEvent>>,
dt: Read<'a, DeltaTime>, dt: Read<'a, DeltaTime>,
time: Read<'a, Time>,
lazy_update: Read<'a, LazyUpdate>, lazy_update: Read<'a, LazyUpdate>,
healths: ReadStorage<'a, Health>, healths: ReadStorage<'a, Health>,
bodies: ReadStorage<'a, Body>, bodies: ReadStorage<'a, Body>,
@ -146,11 +147,11 @@ impl<'a> System<'a> for Sys {
let was_wielded = char_state.is_wield(); let was_wielded = char_state.is_wield();
let poise_state = poise.poise_state(); let poise_state = poise.poise_state();
let pos = pos.0; let pos = pos.0;
if let (Some(stunned_state), impulse_strength) = if let (Some((stunned_state, stunned_duration)), impulse_strength) =
poise_state.poise_effect(was_wielded) poise_state.poise_effect(was_wielded)
{ {
// Reset poise if there is some stunned state to apply // Reset poise if there is some stunned state to apply
poise.reset(); poise.reset(*read_data.time, stunned_duration);
*char_state = stunned_state; *char_state = stunned_state;
outcomes.push(Outcome::PoiseChange { outcomes.push(Outcome::PoiseChange {
pos, pos,

View File

@ -164,6 +164,7 @@ impl<'a> System<'a> for Sys {
pos: pos_b.0, pos: pos_b.0,
ori: read_data.orientations.get(target), ori: read_data.orientations.get(target),
char_state: read_data.char_states.get(target), char_state: read_data.char_states.get(target),
energy: read_data.energies.get(target),
}; };
// PvP check // PvP check

View File

@ -279,6 +279,7 @@ fn dispatch_hit(
pos: target_pos, pos: target_pos,
ori: projectile_target_info.ori, ori: projectile_target_info.ori,
char_state: read_data.character_states.get(target), char_state: read_data.character_states.get(target),
energy: read_data.energies.get(target),
}; };
// TODO: Is it possible to have projectile without body?? // TODO: Is it possible to have projectile without body??

View File

@ -207,6 +207,7 @@ impl<'a> System<'a> for Sys {
pos: pos_b.0, pos: pos_b.0,
ori: read_data.orientations.get(target), ori: read_data.orientations.get(target),
char_state: read_data.character_states.get(target), char_state: read_data.character_states.get(target),
energy: read_data.energies.get(target),
}; };
// PvP check // PvP check

View File

@ -3,8 +3,8 @@ use common::{
comp::{ comp::{
self, self,
skills::{GeneralSkill, Skill}, skills::{GeneralSkill, Skill},
Body, CharacterState, Combo, Energy, Health, Inventory, Poise, Pos, SkillSet, Stats, Body, CharacterState, Combo, Energy, Health, Inventory, Poise, PoiseChange, Pos, SkillSet,
StatsModifier, Stats, StatsModifier,
}, },
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
resources::{DeltaTime, EntitiesDiedLastTick, Time}, resources::{DeltaTime, EntitiesDiedLastTick, Time},
@ -173,7 +173,14 @@ impl<'a> System<'a> for Sys {
if res_poise { if res_poise {
let poise = &mut *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,
time: *read_data.time,
};
poise.change(poise_change);
poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0); poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0);
} }
}, },

View File

@ -48,14 +48,27 @@ enum DamageContrib {
NotFound, NotFound,
} }
pub fn handle_poise(server: &Server, entity: EcsEntity, change: f32, knockback_dir: Vec3<f32>) { pub fn handle_poise(server: &Server, entity: EcsEntity, change: comp::PoiseChange) {
let ecs = &server.state.ecs(); let ecs = &server.state.ecs();
if let Some(character_state) = ecs.read_storage::<CharacterState>().get(entity) { if let Some(character_state) = ecs.read_storage::<CharacterState>().get(entity) {
// Entity is invincible to poise change during stunned/staggered character state // Entity is invincible to poise change during stunned/staggered character
if !character_state.is_stunned() { // state, but the mitigated poise damage is converted to health damage instead
if let Some(mut poise) = ecs.write_storage::<Poise>().get_mut(entity) { if let CharacterState::Stunned(data) = character_state {
poise.change_by(change, knockback_dir); let health_change = change.amount * data.static_data.poise_state.damage_multiplier();
} let time = ecs.read_resource::<Time>();
let health_change = HealthChange {
amount: health_change,
by: None,
cause: None,
time: *time,
};
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
server_eventbus.emit_now(ServerEvent::HealthChange {
entity,
change: health_change,
});
} else if let Some(mut poise) = ecs.write_storage::<Poise>().get_mut(entity) {
poise.change(change);
} }
} }
} }
@ -512,6 +525,7 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
let inventories = ecs.read_storage::<Inventory>(); let inventories = ecs.read_storage::<Inventory>();
let stats = ecs.read_storage::<Stats>(); let stats = ecs.read_storage::<Stats>();
let time = server.state.ecs().read_resource::<Time>();
// Handle health change // Handle health change
if let Some(mut health) = ecs.write_storage::<comp::Health>().get_mut(entity) { if let Some(mut health) = ecs.write_storage::<comp::Health>().get_mut(entity) {
@ -521,11 +535,10 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
value: falldmg, value: falldmg,
}; };
let damage_reduction = Damage::compute_damage_reduction( let damage_reduction = Damage::compute_damage_reduction(
Some(damage),
inventories.get(entity), inventories.get(entity),
stats.get(entity), stats.get(entity),
Some(DamageKind::Crushing),
); );
let time = server.state.ecs().read_resource::<Time>();
let change = let change =
damage.calculate_health_change(damage_reduction, None, false, 0.0, 1.0, *time); damage.calculate_health_change(damage_reduction, None, false, 0.0, 1.0, *time);
health.change_by(change); health.change_by(change);
@ -534,7 +547,14 @@ pub fn handle_land_on_ground(server: &Server, entity: EcsEntity, vel: Vec3<f32>)
if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) { if let Some(mut poise) = ecs.write_storage::<comp::Poise>().get_mut(entity) {
let poise_damage = -(mass.0 * vel.magnitude_squared() / 1500.0); let poise_damage = -(mass.0 * vel.magnitude_squared() / 1500.0);
let poise_change = Poise::apply_poise_reduction(poise_damage, inventories.get(entity)); let poise_change = Poise::apply_poise_reduction(poise_damage, inventories.get(entity));
poise.change_by(poise_change, Vec3::unit_z()); let poise_change = comp::PoiseChange {
amount: poise_change,
impulse: Vec3::unit_z(),
by: None,
cause: None,
time: *time,
};
poise.change(poise_change);
} }
} }
} }
@ -857,6 +877,7 @@ pub fn handle_explosion(server: &Server, pos: Vec3<f32>, explosion: Explosion, o
pos: pos_b.0, pos: pos_b.0,
ori: ori_b_maybe, ori: ori_b_maybe,
char_state: char_state_b_maybe, char_state: char_state_b_maybe,
energy: energies.get(entity_b),
}; };
// PvP check // PvP check
@ -1195,6 +1216,7 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
let ecs = &server.state.ecs(); let ecs = &server.state.ecs();
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>(); let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
let mut outcomes = ecs.write_resource::<Vec<Outcome>>(); let mut outcomes = ecs.write_resource::<Vec<Outcome>>();
let time = ecs.read_resource::<Time>();
if let (Some(mut char_state), Some(mut poise), Some(pos)) = ( if let (Some(mut char_state), Some(mut poise), Some(pos)) = (
ecs.write_storage::<CharacterState>().get_mut(entity), ecs.write_storage::<CharacterState>().get_mut(entity),
@ -1206,11 +1228,13 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
*char_state, *char_state,
CharacterState::SpriteInteract(_) | CharacterState::UseItem(_) CharacterState::SpriteInteract(_) | CharacterState::UseItem(_)
) { ) {
let poise_state = comp::poise::PoiseState::Dazed; let poise_state = comp::poise::PoiseState::Interrupted;
let was_wielded = char_state.is_wield(); let was_wielded = char_state.is_wield();
if let (Some(stunned_state), impulse_strength) = poise_state.poise_effect(was_wielded) { if let (Some((stunned_state, stunned_duration)), impulse_strength) =
poise_state.poise_effect(was_wielded)
{
// Reset poise if there is some stunned state to apply // Reset poise if there is some stunned state to apply
poise.reset(); poise.reset(*time, stunned_duration);
*char_state = stunned_state; *char_state = stunned_state;
outcomes.push(Outcome::PoiseChange { outcomes.push(Outcome::PoiseChange {
pos: pos.0, pos: pos.0,

View File

@ -96,11 +96,7 @@ impl Server {
ServerEvent::HealthChange { entity, change } => { ServerEvent::HealthChange { entity, change } => {
handle_health_change(self, entity, change) handle_health_change(self, entity, change)
}, },
ServerEvent::PoiseChange { ServerEvent::PoiseChange { entity, change } => handle_poise(self, entity, change),
entity,
change,
kb_dir,
} => handle_poise(self, entity, change, kb_dir),
ServerEvent::Delete(entity) => handle_delete(self, entity), ServerEvent::Delete(entity) => handle_delete(self, entity),
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause), ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip), ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),

View File

@ -139,9 +139,9 @@ impl StateExt for State {
let time = self.ecs().read_resource::<Time>(); let time = self.ecs().read_resource::<Time>();
let change = damage.calculate_health_change( let change = damage.calculate_health_change(
combat::Damage::compute_damage_reduction( combat::Damage::compute_damage_reduction(
Some(damage),
inventories.get(entity), inventories.get(entity),
stats.get(entity), stats.get(entity),
Some(damage.kind),
), ),
damage_contributor, damage_contributor,
false, false,
@ -164,10 +164,24 @@ impl StateExt for State {
.get(entity) .get(entity)
{ {
if !character_state.is_stunned() { if !character_state.is_stunned() {
let groups = self.ecs().read_storage::<comp::Group>();
let damage_contributor = source.and_then(|uid| {
self.ecs().entity_from_uid(uid.0).map(|attacker_entity| {
DamageContributor::new(uid, groups.get(attacker_entity).cloned())
})
});
let time = self.ecs().read_resource::<Time>();
let poise_change = comp::PoiseChange {
amount: change,
impulse: Vec3::zero(),
cause: None,
by: damage_contributor,
time: *time,
};
self.ecs() self.ecs()
.write_storage::<comp::Poise>() .write_storage::<comp::Poise>()
.get_mut(entity) .get_mut(entity)
.map(|mut poise| poise.change_by(change, Vec3::zero())); .map(|mut poise| poise.change(poise_change));
} }
} }
}, },

View File

@ -935,7 +935,7 @@ impl<'a> Widget for Bag<'a> {
let protection_txt = format!( let protection_txt = format!(
"{}%", "{}%",
(100.0 (100.0
* Damage::compute_damage_reduction(Some(inventory), Some(self.stats), None,)) * Damage::compute_damage_reduction(None, Some(inventory), Some(self.stats)))
as i32 as i32
); );
let health_txt = format!("{}", self.health.maximum().round() as usize); let health_txt = format!("{}", self.health.maximum().round() as usize);