mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'sam/damage-types' into 'master'
Physical damage types now have effects See merge request veloren/veloren!3087
This commit is contained in:
commit
87769e7e55
@ -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
|
||||
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
@ -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,
|
||||
|
@ -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((
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
@ -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,
|
||||
|
@ -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((
|
||||
|
@ -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,
|
||||
)
|
||||
)
|
@ -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<f32>,
|
||||
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<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
|
||||
#[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<Self>,
|
||||
inventory: Option<&Inventory>,
|
||||
stats: Option<&Stats>,
|
||||
kind: Option<DamageKind>,
|
||||
) -> f32 {
|
||||
let inventory_dr = if let Some(inventory) = inventory {
|
||||
let protection = inventory
|
||||
@ -696,12 +778,19 @@ impl Damage {
|
||||
})
|
||||
.sum::<Option<f32>>();
|
||||
|
||||
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
|
||||
|
@ -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<DamageContributor>,
|
||||
/// The category of action that resulted in the health change
|
||||
|
@ -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::{
|
||||
|
@ -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<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)]
|
||||
/// 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<Time>,
|
||||
}
|
||||
|
||||
/// States to define effects of a poise change
|
||||
@ -54,7 +74,9 @@ pub enum 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::{
|
||||
stunned::{Data, StaticData},
|
||||
utils::StageSection,
|
||||
@ -64,39 +86,55 @@ impl PoiseState {
|
||||
let (charstate_parameters, impulse) = match self {
|
||||
PoiseState::Normal => (None, None),
|
||||
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,
|
||||
),
|
||||
PoiseState::Stunned => (
|
||||
Some((Duration::from_millis(300), Duration::from_millis(300), 0.65)),
|
||||
Some(5.0),
|
||||
Some((Duration::from_millis(400), Duration::from_millis(400), 0.5)),
|
||||
None,
|
||||
),
|
||||
PoiseState::Dazed => (
|
||||
Some((Duration::from_millis(600), Duration::from_millis(250), 0.45)),
|
||||
Some(10.0),
|
||||
Some((Duration::from_millis(750), Duration::from_millis(450), 0.2)),
|
||||
None,
|
||||
),
|
||||
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),
|
||||
),
|
||||
};
|
||||
(
|
||||
charstate_parameters.map(|(buildup_duration, recover_duration, movement_speed)| {
|
||||
CharacterState::Stunned(Data {
|
||||
static_data: StaticData {
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
movement_speed,
|
||||
poise_state: *self,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
was_wielded,
|
||||
})
|
||||
(
|
||||
CharacterState::Stunned(Data {
|
||||
static_data: StaticData {
|
||||
buildup_duration,
|
||||
recover_duration,
|
||||
movement_speed,
|
||||
poise_state: *self,
|
||||
},
|
||||
timer: Duration::default(),
|
||||
stage_section: StageSection::Buildup,
|
||||
was_wielded,
|
||||
}),
|
||||
buildup_duration.as_secs_f64() + recover_duration.as_secs_f64(),
|
||||
)
|
||||
}),
|
||||
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 {
|
||||
@ -107,6 +145,9 @@ impl Poise {
|
||||
/// can fit into an f32 with no loss to precision
|
||||
// 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;
|
||||
/// 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.
|
||||
// This value is chosen as anything smaller than this is more precise than our
|
||||
// units of poise.
|
||||
@ -146,17 +187,27 @@ impl Poise {
|
||||
maximum: poise,
|
||||
last_change: Dir::default(),
|
||||
regen_rate: 0.0,
|
||||
last_stun_time: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn change_by(&mut self, change: f32, impulse: Vec3<f32>) {
|
||||
self.current = (((self.current() + change).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();
|
||||
pub fn change(&mut self, change: PoiseChange) {
|
||||
match self.last_stun_time {
|
||||
Some(last_time) if last_time.0 + Poise::POISE_BUFFER_TIME > change.time.0 => {},
|
||||
_ => {
|
||||
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
|
||||
/// 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
|
||||
pub fn poise_state(&self) -> PoiseState {
|
||||
match self.current() {
|
||||
x if x > 70.0 => PoiseState::Normal,
|
||||
x if x > 50.0 => PoiseState::Interrupted,
|
||||
x if x > 40.0 => PoiseState::Stunned,
|
||||
x if x > 20.0 => PoiseState::Dazed,
|
||||
x if x > 50.0 => PoiseState::Normal,
|
||||
x if x > 30.0 => PoiseState::Interrupted,
|
||||
x if x > 15.0 => PoiseState::Stunned,
|
||||
x if x > 5.0 => PoiseState::Dazed,
|
||||
_ => PoiseState::KnockedDown,
|
||||
}
|
||||
}
|
||||
|
@ -53,8 +53,7 @@ pub enum ServerEvent {
|
||||
},
|
||||
PoiseChange {
|
||||
entity: EcsEntity,
|
||||
change: f32,
|
||||
kb_dir: Vec3<f32>,
|
||||
change: comp::PoiseChange,
|
||||
},
|
||||
Delete(EcsEntity),
|
||||
Destroy {
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -12,7 +12,7 @@ use common::{
|
||||
},
|
||||
event::{EventBus, LocalEvent, ServerEvent},
|
||||
outcome::Outcome,
|
||||
resources::DeltaTime,
|
||||
resources::{DeltaTime, Time},
|
||||
states::{
|
||||
behavior::{JoinData, JoinStruct},
|
||||
idle,
|
||||
@ -28,6 +28,7 @@ pub struct ReadData<'a> {
|
||||
server_bus: Read<'a, EventBus<ServerEvent>>,
|
||||
local_bus: Read<'a, EventBus<LocalEvent>>,
|
||||
dt: Read<'a, DeltaTime>,
|
||||
time: Read<'a, Time>,
|
||||
lazy_update: Read<'a, LazyUpdate>,
|
||||
healths: ReadStorage<'a, Health>,
|
||||
bodies: ReadStorage<'a, Body>,
|
||||
@ -146,11 +147,11 @@ impl<'a> System<'a> for Sys {
|
||||
let was_wielded = char_state.is_wield();
|
||||
let poise_state = poise.poise_state();
|
||||
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)
|
||||
{
|
||||
// Reset poise if there is some stunned state to apply
|
||||
poise.reset();
|
||||
poise.reset(*read_data.time, stunned_duration);
|
||||
*char_state = stunned_state;
|
||||
outcomes.push(Outcome::PoiseChange {
|
||||
pos,
|
||||
|
@ -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
|
||||
|
@ -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??
|
||||
|
@ -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
|
||||
|
@ -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,14 @@ 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,
|
||||
time: *read_data.time,
|
||||
};
|
||||
poise.change(poise_change);
|
||||
poise.regen_rate = (poise.regen_rate + POISE_REGEN_ACCEL * dt).min(10.0);
|
||||
}
|
||||
},
|
||||
|
@ -48,14 +48,27 @@ enum DamageContrib {
|
||||
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();
|
||||
if let Some(character_state) = ecs.read_storage::<CharacterState>().get(entity) {
|
||||
// Entity is invincible to poise change during stunned/staggered character state
|
||||
if !character_state.is_stunned() {
|
||||
if let Some(mut poise) = ecs.write_storage::<Poise>().get_mut(entity) {
|
||||
poise.change_by(change, knockback_dir);
|
||||
}
|
||||
// Entity is invincible to poise change during stunned/staggered character
|
||||
// state, but the mitigated poise damage is converted to health damage instead
|
||||
if let CharacterState::Stunned(data) = character_state {
|
||||
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 stats = ecs.read_storage::<Stats>();
|
||||
let time = server.state.ecs().read_resource::<Time>();
|
||||
|
||||
// Handle health change
|
||||
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,
|
||||
};
|
||||
let damage_reduction = Damage::compute_damage_reduction(
|
||||
Some(damage),
|
||||
inventories.get(entity),
|
||||
stats.get(entity),
|
||||
Some(DamageKind::Crushing),
|
||||
);
|
||||
let time = server.state.ecs().read_resource::<Time>();
|
||||
let change =
|
||||
damage.calculate_health_change(damage_reduction, None, false, 0.0, 1.0, *time);
|
||||
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) {
|
||||
let poise_damage = -(mass.0 * vel.magnitude_squared() / 1500.0);
|
||||
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,
|
||||
ori: ori_b_maybe,
|
||||
char_state: char_state_b_maybe,
|
||||
energy: energies.get(entity_b),
|
||||
};
|
||||
|
||||
// PvP check
|
||||
@ -1195,6 +1216,7 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
|
||||
let ecs = &server.state.ecs();
|
||||
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
|
||||
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)) = (
|
||||
ecs.write_storage::<CharacterState>().get_mut(entity),
|
||||
@ -1206,11 +1228,13 @@ pub fn handle_entity_attacked_hook(server: &Server, entity: EcsEntity) {
|
||||
*char_state,
|
||||
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();
|
||||
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
|
||||
poise.reset();
|
||||
poise.reset(*time, stunned_duration);
|
||||
*char_state = stunned_state;
|
||||
outcomes.push(Outcome::PoiseChange {
|
||||
pos: pos.0,
|
||||
|
@ -96,11 +96,7 @@ impl Server {
|
||||
ServerEvent::HealthChange { entity, change } => {
|
||||
handle_health_change(self, entity, change)
|
||||
},
|
||||
ServerEvent::PoiseChange {
|
||||
entity,
|
||||
change,
|
||||
kb_dir,
|
||||
} => handle_poise(self, entity, change, kb_dir),
|
||||
ServerEvent::PoiseChange { entity, change } => handle_poise(self, entity, change),
|
||||
ServerEvent::Delete(entity) => handle_delete(self, entity),
|
||||
ServerEvent::Destroy { entity, cause } => handle_destroy(self, entity, cause),
|
||||
ServerEvent::InventoryManip(entity, manip) => handle_inventory(self, entity, manip),
|
||||
|
@ -139,9 +139,9 @@ impl StateExt for State {
|
||||
let time = self.ecs().read_resource::<Time>();
|
||||
let change = damage.calculate_health_change(
|
||||
combat::Damage::compute_damage_reduction(
|
||||
Some(damage),
|
||||
inventories.get(entity),
|
||||
stats.get(entity),
|
||||
Some(damage.kind),
|
||||
),
|
||||
damage_contributor,
|
||||
false,
|
||||
@ -164,10 +164,24 @@ impl StateExt for State {
|
||||
.get(entity)
|
||||
{
|
||||
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()
|
||||
.write_storage::<comp::Poise>()
|
||||
.get_mut(entity)
|
||||
.map(|mut poise| poise.change_by(change, Vec3::zero()));
|
||||
.map(|mut poise| poise.change(poise_change));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -935,7 +935,7 @@ impl<'a> Widget for Bag<'a> {
|
||||
let protection_txt = format!(
|
||||
"{}%",
|
||||
(100.0
|
||||
* Damage::compute_damage_reduction(Some(inventory), Some(self.stats), None,))
|
||||
* Damage::compute_damage_reduction(None, Some(inventory), Some(self.stats)))
|
||||
as i32
|
||||
);
|
||||
let health_txt = format!("{}", self.health.maximum().round() as usize);
|
||||
|
Loading…
Reference in New Issue
Block a user