Merge branch 'srpapinha/parried-debuff-rework' into 'master'

Parried debuff rework

See merge request veloren/veloren!4416
This commit is contained in:
Samuel Keiffer 2024-05-27 22:01:28 +00:00
commit 9264c10289
34 changed files with 225 additions and 36 deletions

View File

@ -63,6 +63,7 @@ pub const MAX_BLOCK_POISE_COST: f32 = 25.0;
pub const PARRY_BONUS_MULTIPLIER: f32 = 2.0; pub const PARRY_BONUS_MULTIPLIER: f32 = 2.0;
pub const FALLBACK_BLOCK_STRENGTH: f32 = 5.0; pub const FALLBACK_BLOCK_STRENGTH: f32 = 5.0;
pub const BEHIND_TARGET_ANGLE: f32 = 45.0; pub const BEHIND_TARGET_ANGLE: f32 = 45.0;
pub const BASE_PARRIED_POISE_PUNISHMENT: f32 = 100.0 / 3.5;
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct AttackerInfo<'a> { pub struct AttackerInfo<'a> {
@ -76,6 +77,7 @@ pub struct AttackerInfo<'a> {
pub mass: Option<&'a Mass>, pub mass: Option<&'a Mass>,
} }
#[derive(Copy, Clone)]
pub struct TargetInfo<'a> { pub struct TargetInfo<'a> {
pub entity: EcsEntity, pub entity: EcsEntity,
pub uid: Uid, pub uid: Uid,
@ -187,6 +189,7 @@ impl Attack {
defender: target.entity, defender: target.entity,
attacker: attacker.map(|a| a.entity), attacker: attacker.map(|a| a.entity),
source, source,
poise_multiplier: 2.0 - (damage_value / block_strength).min(1.0),
}); });
} }
@ -301,10 +304,22 @@ impl Attack {
(matches!(attack_effect.target, Some(GroupTarget::OutOfGroup)) && !allow_friendly_fire) (matches!(attack_effect.target, Some(GroupTarget::OutOfGroup)) && !allow_friendly_fire)
&& (target_dodging || !permit_pvp) && (target_dodging || !permit_pvp)
}; };
let precision_mult = attacker
let from_precision_mult = attacker
.and_then(|a| a.stats) .and_then(|a| a.stats)
.and_then(|s| s.precision_multiplier_override) .and_then(|s| s.precision_multiplier_override)
.or(precision_mult); .or(precision_mult);
let from_precision_vulnerability_mult = target
.stats
.and_then(|s| s.precision_vulnerability_multiplier_override);
let precision_mult = match (from_precision_mult, from_precision_vulnerability_mult) {
(Some(a), Some(b)) => Some(a.max(b)),
(Some(a), None) | (None, Some(a)) => Some(a),
(None, None) => None,
};
let mut is_applied = false; let mut is_applied = false;
let mut accumulated_damage = 0.0; let mut accumulated_damage = 0.0;
let damage_modifier = attacker let damage_modifier = attacker

View File

@ -2402,6 +2402,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
}, },
timer: Duration::default(), timer: Duration::default(),
stage_section: StageSection::Buildup, stage_section: StageSection::Buildup,
is_parry: false,
}), }),
CharacterAbility::Roll { CharacterAbility::Roll {
energy_cost: _, energy_cost: _,

View File

@ -436,7 +436,10 @@ impl BuffKind {
BuffEffect::PoiseReduction(nn_scaling(data.strength)), BuffEffect::PoiseReduction(nn_scaling(data.strength)),
BuffEffect::PoiseDamageFromLostHealth(data.strength), BuffEffect::PoiseDamageFromLostHealth(data.strength),
], ],
BuffKind::Parried => vec![BuffEffect::AttackSpeed(0.5)], BuffKind::Parried => vec![
BuffEffect::RecoverySpeed(0.25),
BuffEffect::PrecisionVulnerabilityOverride(1.0),
],
//TODO: Handle potion sickness in a more general way. //TODO: Handle potion sickness in a more general way.
BuffKind::PotionSickness => vec![ BuffKind::PotionSickness => vec![
BuffEffect::HealReduction(data.strength), BuffEffect::HealReduction(data.strength),
@ -672,6 +675,8 @@ pub enum BuffEffect {
MovementSpeed(f32), MovementSpeed(f32),
/// Modifies attack speed of target /// Modifies attack speed of target
AttackSpeed(f32), AttackSpeed(f32),
/// Modifies recovery speed of target
RecoverySpeed(f32),
/// Modifies ground friction of target /// Modifies ground friction of target
GroundFriction(f32), GroundFriction(f32),
/// Reduces poise damage taken after armor is accounted for by this fraction /// Reduces poise damage taken after armor is accounted for by this fraction
@ -686,6 +691,8 @@ pub enum BuffEffect {
AttackDamage(f32), AttackDamage(f32),
/// Overrides the precision multiplier applied to an attack /// Overrides the precision multiplier applied to an attack
PrecisionOverride(f32), PrecisionOverride(f32),
/// Overrides the precision multiplier applied to an incoming attack
PrecisionVulnerabilityOverride(f32),
/// Changes body. /// Changes body.
BodyChange(Body), BodyChange(Body),
BuffImmunity(BuffKind), BuffImmunity(BuffKind),

View File

@ -61,11 +61,13 @@ pub struct Stats {
pub move_speed_modifier: f32, pub move_speed_modifier: f32,
pub jump_modifier: f32, pub jump_modifier: f32,
pub attack_speed_modifier: f32, pub attack_speed_modifier: f32,
pub recovery_speed_modifier: f32,
pub friction_modifier: f32, pub friction_modifier: f32,
pub max_energy_modifiers: StatsModifier, pub max_energy_modifiers: StatsModifier,
pub poise_damage_modifier: f32, pub poise_damage_modifier: f32,
pub attack_damage_modifier: f32, pub attack_damage_modifier: f32,
pub precision_multiplier_override: Option<f32>, pub precision_multiplier_override: Option<f32>,
pub precision_vulnerability_multiplier_override: Option<f32>,
pub swim_speed_modifier: f32, pub swim_speed_modifier: f32,
/// This adds effects to any attacks that the entity makes /// This adds effects to any attacks that the entity makes
pub effects_on_attack: Vec<AttackEffect>, pub effects_on_attack: Vec<AttackEffect>,
@ -93,11 +95,13 @@ impl Stats {
move_speed_modifier: 1.0, move_speed_modifier: 1.0,
jump_modifier: 1.0, jump_modifier: 1.0,
attack_speed_modifier: 1.0, attack_speed_modifier: 1.0,
recovery_speed_modifier: 1.0,
friction_modifier: 1.0, friction_modifier: 1.0,
max_energy_modifiers: StatsModifier::default(), max_energy_modifiers: StatsModifier::default(),
poise_damage_modifier: 1.0, poise_damage_modifier: 1.0,
attack_damage_modifier: 1.0, attack_damage_modifier: 1.0,
precision_multiplier_override: None, precision_multiplier_override: None,
precision_vulnerability_multiplier_override: None,
swim_speed_modifier: 1.0, swim_speed_modifier: 1.0,
effects_on_attack: Vec::new(), effects_on_attack: Vec::new(),
mitigations_penetration: 0.0, mitigations_penetration: 0.0,

View File

@ -332,6 +332,7 @@ pub struct ParryHookEvent {
pub defender: EcsEntity, pub defender: EcsEntity,
pub attacker: Option<EcsEntity>, pub attacker: Option<EcsEntity>,
pub source: AttackSource, pub source: AttackSource,
pub poise_multiplier: f32,
} }
pub struct RequestSiteInfoEvent { pub struct RequestSiteInfoEvent {

View File

@ -134,7 +134,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
update.character = CharacterState::BasicAura(Data { update.character = CharacterState::BasicAura(Data {
static_data: self.static_data.clone(), static_data: self.static_data.clone(),
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -216,7 +216,11 @@ impl CharacterBehavior for Data {
StageSection::Recover => { StageSection::Recover => {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
update.character = CharacterState::BasicBeam(Data { update.character = CharacterState::BasicBeam(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -51,6 +51,8 @@ pub struct Data {
pub timer: Duration, pub timer: Duration,
/// What section the character stage is in /// What section the character stage is in
pub stage_section: StageSection, pub stage_section: StageSection,
// Whether there was parry
pub is_parry: bool,
} }
impl CharacterBehavior for Data { impl CharacterBehavior for Data {
@ -100,10 +102,16 @@ impl CharacterBehavior for Data {
} }
}, },
StageSection::Recover => { StageSection::Recover => {
if self.timer < self.static_data.recover_duration { if (self.static_data.parry_window.recover || !self.is_parry)
&& self.timer < self.static_data.recover_duration
{
// Recovery // Recovery
update.character = CharacterState::BasicBlock(Data { update.character = CharacterState::BasicBlock(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -133,7 +133,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
update.character = CharacterState::BasicMelee(Data { update.character = CharacterState::BasicMelee(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -154,7 +154,11 @@ impl CharacterBehavior for Data {
} else if self.timer < self.static_data.recover_duration { } else if self.timer < self.static_data.recover_duration {
// Recovers // Recovers
update.character = CharacterState::BasicRanged(Data { update.character = CharacterState::BasicRanged(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -249,7 +249,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
update.character = CharacterState::BasicSummon(Data { update.character = CharacterState::BasicSummon(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -78,7 +78,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
update.character = CharacterState::Blink(Data { update.character = CharacterState::Blink(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -206,7 +206,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovers // Recovers
update.character = CharacterState::ChargedMelee(Data { update.character = CharacterState::ChargedMelee(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -171,7 +171,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovers // Recovers
update.character = CharacterState::ChargedRanged(Data { update.character = CharacterState::ChargedRanged(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -234,7 +234,11 @@ impl CharacterBehavior for Data {
if self.timer < strike_data.recover_duration { if self.timer < strike_data.recover_duration {
// Recovery // Recovery
if let CharacterState::ComboMelee2(c) = &mut update.character { if let CharacterState::ComboMelee2(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None); c.timer = tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
);
} }
} else { } else {
// Return to wielding // Return to wielding

View File

@ -170,7 +170,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recover // Recover
update.character = CharacterState::DashMelee(Data { update.character = CharacterState::DashMelee(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -131,7 +131,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Complete recovery delay before finishing state // Complete recovery delay before finishing state
if let CharacterState::DiveMelee(c) = &mut update.character { if let CharacterState::DiveMelee(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None); c.timer = tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
);
} }
} else { } else {
// Done // Done

View File

@ -128,7 +128,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
if let CharacterState::FinisherMelee(c) = &mut update.character { if let CharacterState::FinisherMelee(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None); c.timer = tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
);
} }
} else { } else {
// Done // Done

View File

@ -139,14 +139,22 @@ impl CharacterBehavior for Data {
); );
update.character = CharacterState::LeapMelee(Data { update.character = CharacterState::LeapMelee(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
exhausted: true, exhausted: true,
..*self ..*self
}); });
} else if self.timer < self.static_data.recover_duration { } else if self.timer < self.static_data.recover_duration {
// Complete recovery delay before finishing state // Complete recovery delay before finishing state
update.character = CharacterState::LeapMelee(Data { update.character = CharacterState::LeapMelee(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -248,7 +248,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovers // Recovers
update.character = CharacterState::LeapShockwave(Data { update.character = CharacterState::LeapShockwave(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -127,7 +127,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recover // Recover
if let CharacterState::RapidMelee(c) = &mut update.character { if let CharacterState::RapidMelee(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None); c.timer = tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
);
} }
} else { } else {
// Done // Done

View File

@ -187,7 +187,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recover from attack // Recover from attack
update.character = CharacterState::RepeaterRanged(Data { update.character = CharacterState::RepeaterRanged(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -94,7 +94,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
if let CharacterState::RiposteMelee(c) = &mut update.character { if let CharacterState::RiposteMelee(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None); c.timer = tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
);
} }
} else { } else {
// Done // Done

View File

@ -119,7 +119,11 @@ impl CharacterBehavior for Data {
{ {
// Recover // Recover
update.character = CharacterState::Roll(Data { update.character = CharacterState::Roll(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -170,7 +170,11 @@ impl CharacterBehavior for Data {
StageSection::Recover => { StageSection::Recover => {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
update.character = CharacterState::SelfBuff(Data { update.character = CharacterState::SelfBuff(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -174,7 +174,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovers // Recovers
update.character = CharacterState::Shockwave(Data { update.character = CharacterState::Shockwave(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -95,7 +95,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
if let CharacterState::SpriteInteract(c) = &mut update.character { if let CharacterState::SpriteInteract(c) = &mut update.character {
c.timer = tick_attack_or_default(data, self.timer, None); c.timer = tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
);
} }
} else { } else {
// Create inventory manipulation event // Create inventory manipulation event

View File

@ -183,7 +183,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
update.character = CharacterState::SpriteSummon(Data { update.character = CharacterState::SpriteSummon(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -140,7 +140,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
update.character = CharacterState::StaticAura(Data { update.character = CharacterState::StaticAura(Data {
static_data: self.static_data.clone(), static_data: self.static_data.clone(),
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -66,7 +66,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
// Recovery // Recovery
update.character = CharacterState::Stunned(Data { update.character = CharacterState::Stunned(Data {
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -114,7 +114,11 @@ impl CharacterBehavior for Data {
if self.timer < self.static_data.recover_duration { if self.timer < self.static_data.recover_duration {
update.character = CharacterState::Transform(Data { update.character = CharacterState::Transform(Data {
static_data: self.static_data.clone(), static_data: self.static_data.clone(),
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -120,7 +120,11 @@ impl CharacterBehavior for Data {
// Recovery // Recovery
update.character = CharacterState::UseItem(Data { update.character = CharacterState::UseItem(Data {
static_data: self.static_data.clone(), static_data: self.static_data.clone(),
timer: tick_attack_or_default(data, self.timer, None), timer: tick_attack_or_default(
data,
self.timer,
Some(data.stats.recovery_speed_modifier),
),
..*self ..*self
}); });
} else { } else {

View File

@ -755,6 +755,9 @@ fn execute_effect(
BuffEffect::AttackSpeed(speed) => { BuffEffect::AttackSpeed(speed) => {
stat.attack_speed_modifier *= *speed; stat.attack_speed_modifier *= *speed;
}, },
BuffEffect::RecoverySpeed(speed) => {
stat.recovery_speed_modifier *= *speed;
},
BuffEffect::GroundFriction(gf) => { BuffEffect::GroundFriction(gf) => {
stat.friction_modifier *= *gf; stat.friction_modifier *= *gf;
}, },
@ -781,6 +784,13 @@ fn execute_effect(
.map(|mult| mult.min(*val)) .map(|mult| mult.min(*val))
.or(Some(*val)); .or(Some(*val));
}, },
BuffEffect::PrecisionVulnerabilityOverride(val) => {
// Use higher of precision multiplier overrides
stat.precision_vulnerability_multiplier_override = stat
.precision_vulnerability_multiplier_override
.map(|mult| mult.max(*val))
.or(Some(*val));
},
BuffEffect::BodyChange(b) => { BuffEffect::BodyChange(b) => {
// For when an entity is under the effects of multiple de/buffs that change the // For when an entity is under the effects of multiple de/buffs that change the
// body, to avoid flickering between many bodies only change the body if the // body, to avoid flickering between many bodies only change the body if the

View File

@ -18,7 +18,7 @@ use crate::{
#[cfg(feature = "worldgen")] #[cfg(feature = "worldgen")]
use common::rtsim::{Actor, RtSimEntity}; use common::rtsim::{Actor, RtSimEntity};
use common::{ use common::{
combat::{self, AttackSource, DamageContributor, DeathEffect}, combat::{self, AttackSource, DamageContributor, DeathEffect, BASE_PARRIED_POISE_PUNISHMENT},
comp::{ comp::{
self, self,
aura::{self, EnteredAuras}, aura::{self, EnteredAuras},
@ -28,7 +28,7 @@ use common::{
item::flatten_counted_items, item::flatten_counted_items,
loot_owner::LootOwnerKind, loot_owner::LootOwnerKind,
Alignment, Auras, Body, CharacterState, Energy, Group, Health, Inventory, Object, Alignment, Auras, Body, CharacterState, Energy, Group, Health, Inventory, Object,
PickupItem, Player, Poise, Pos, Presence, PresenceKind, SkillSet, Stats, PickupItem, Player, Poise, PoiseChange, Pos, Presence, PresenceKind, SkillSet, Stats,
BASE_ABILITY_LIMIT, BASE_ABILITY_LIMIT,
}, },
consts::TELEPORTER_RADIUS, consts::TELEPORTER_RADIUS,
@ -1746,18 +1746,31 @@ impl ServerEvent for ParryHookEvent {
type SystemData<'a> = ( type SystemData<'a> = (
Read<'a, Time>, Read<'a, Time>,
Read<'a, EventBus<EnergyChangeEvent>>, Read<'a, EventBus<EnergyChangeEvent>>,
Read<'a, EventBus<PoiseChangeEvent>>,
Read<'a, EventBus<BuffEvent>>, Read<'a, EventBus<BuffEvent>>,
WriteStorage<'a, CharacterState>, WriteStorage<'a, CharacterState>,
ReadStorage<'a, Uid>, ReadStorage<'a, Uid>,
ReadStorage<'a, Stats>, ReadStorage<'a, Stats>,
ReadStorage<'a, comp::Mass>, ReadStorage<'a, comp::Mass>,
ReadStorage<'a, Inventory>,
); );
fn handle( fn handle(
events: impl ExactSizeIterator<Item = Self>, events: impl ExactSizeIterator<Item = Self>,
(time, energy_change_events, buff_events, mut character_states, uids, stats, masses): Self::SystemData<'_>, (
time,
energy_change_events,
poise_change_events,
buff_events,
mut character_states,
uids,
stats,
masses,
inventories,
): Self::SystemData<'_>,
) { ) {
let mut energy_change_emitter = energy_change_events.emitter(); let mut energy_change_emitter = energy_change_events.emitter();
let mut poise_change_emitter = poise_change_events.emitter();
let mut buff_emitter = buff_events.emitter(); let mut buff_emitter = buff_events.emitter();
for ev in events { for ev in events {
if let Some(mut char_state) = character_states.get_mut(ev.defender) { if let Some(mut char_state) = character_states.get_mut(ev.defender) {
@ -1773,6 +1786,7 @@ impl ServerEvent for ParryHookEvent {
entity: ev.defender, entity: ev.defender,
change: c.static_data.energy_regen, change: c.static_data.energy_regen,
}); });
c.is_parry = true;
false false
}, },
_ => false, _ => false,
@ -1787,8 +1801,8 @@ impl ServerEvent for ParryHookEvent {
if let Some(attacker) = ev.attacker if let Some(attacker) = ev.attacker
&& matches!(ev.source, AttackSource::Melee) && matches!(ev.source, AttackSource::Melee)
{ {
// When attacker is parried, add the parried debuff for 2 seconds, which slows // When attacker is parried, the debuff lasts 2 seconds, the attacker takes
// them // poise damage, get precision vulnerability and get slower recovery speed
let data = buff::BuffData::new(1.0, Some(Secs(2.0))); let data = buff::BuffData::new(1.0, Some(Secs(2.0)));
let source = if let Some(uid) = uids.get(ev.defender) { let source = if let Some(uid) = uids.get(ev.defender) {
BuffSource::Character { by: *uid } BuffSource::Character { by: *uid }
@ -1812,6 +1826,27 @@ impl ServerEvent for ParryHookEvent {
entity: attacker, entity: attacker,
buff_change: buff::BuffChange::Add(buff), buff_change: buff::BuffChange::Add(buff),
}); });
let attacker_poise_change = Poise::apply_poise_reduction(
ev.poise_multiplier.clamp(1.0, 2.0) * BASE_PARRIED_POISE_PUNISHMENT,
inventories.get(attacker),
&MaterialStatManifest::load().read(),
character_states.get(attacker),
stats.get(attacker),
);
poise_change_emitter.emit(PoiseChangeEvent {
entity: attacker,
change: PoiseChange {
amount: -attacker_poise_change,
impulse: Vec3::zero(),
by: uids
.get(ev.defender)
.map(|d| DamageContributor::new(*d, None)),
cause: Some(DamageSource::Melee),
time: *time,
},
});
} }
} }
} }