idek where most of these came from

This commit is contained in:
Maveth 2022-01-17 10:50:18 -06:00
commit b9ad0307b5
30 changed files with 298 additions and 105 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)
- 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

View File

@ -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,
)
)

View File

@ -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,

View File

@ -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((

View File

@ -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,

View File

@ -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,

View File

@ -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,

View File

@ -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,
)
)

View File

@ -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,

View File

@ -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((

View File

@ -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,
)
)

View File

@ -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"

View File

@ -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

View File

@ -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

View File

@ -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::{

View File

@ -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,
}
}

View File

@ -16,6 +16,8 @@ use tracing::{trace, warn};
pub mod skills;
#[cfg(test)] mod test;
/// BTreeSet is used here to ensure that skills are ordered. This is important
/// to ensure that the hash created from it is consistent so that we don't
/// needlessly force a respec when loading skills from persistence.

View File

@ -2,6 +2,7 @@ use super::*;
use crate::comp::{skillset::SkillPrerequisitesMap, Skill};
use hashbrown::HashMap;
// Unneeded cfg(test) here keeps rust-analyzer happy
#[cfg(test)]
use petgraph::{algo::is_cyclic_undirected, graph::UnGraph};
@ -12,9 +13,7 @@ fn check_cyclic_skill_deps() {
let mut graph = UnGraph::new_undirected();
let mut nodes = HashMap::<Skill, _>::new();
let mut add_node = |graph: &mut UnGraph<Skill, _>, node: Skill| {
*nodes
.entry(node.clone())
.or_insert_with(|| graph.add_node(node.clone()))
*nodes.entry(node).or_insert_with(|| graph.add_node(node))
};
for (skill, prereqs) in skill_prereqs.iter() {

View File

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

View File

@ -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

View File

@ -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() {

View File

@ -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,

View File

@ -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

View File

@ -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??

View File

@ -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

View File

@ -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);
}
},

View File

@ -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,

View File

@ -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),

View File

@ -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));
}
}
},

View File

@ -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);