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:
@ -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
|
||||||
|
|
||||||
|
@ -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,
|
||||||
)
|
)
|
@ -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,
|
||||||
|
@ -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((
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
|
@ -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,
|
||||||
)
|
)
|
@ -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,
|
||||||
|
@ -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((
|
||||||
|
@ -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,
|
||||||
)
|
)
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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::{
|
||||||
|
@ -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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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
|
||||||
|
@ -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() {
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
@ -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??
|
||||||
|
@ -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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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,
|
||||||
|
@ -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),
|
||||||
|
@ -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));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -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);
|
||||||
|
Reference in New Issue
Block a user