From 8a578bf3f6b22158b87ed4d603c5b83f92410150 Mon Sep 17 00:00:00 2001 From: Sam Date: Sat, 5 Mar 2022 13:52:43 -0500 Subject: [PATCH] Parrying abilities --- assets/common/abilities/shield/block.ron | 4 + .../common/abilities/sword/parrying_combo.ron | 79 +++++++++--- .../abilities/sword/parrying_counter.ron | 45 +++---- .../common/abilities/sword/parrying_parry.ron | 33 ++--- .../abilities/sword/parrying_riposte.ron | 26 ++-- common/src/combat.rs | 40 +++--- common/src/comp/ability.rs | 81 ++++++++++-- common/src/comp/character_state.rs | 54 +++++++- common/src/event.rs | 6 +- common/src/states/basic_block.rs | 18 +++ common/src/states/finisher_melee.rs | 1 - common/src/states/mod.rs | 1 + common/src/states/riposte_melee.rs | 115 ++++++++++++++++++ common/systems/src/stats.rs | 3 +- server/src/events/entity_manipulation.rs | 35 ++++-- server/src/events/mod.rs | 12 +- voxygen/src/scene/figure/cache.rs | 2 +- 17 files changed, 427 insertions(+), 128 deletions(-) create mode 100644 common/src/states/riposte_melee.rs diff --git a/assets/common/abilities/shield/block.ron b/assets/common/abilities/shield/block.ron index f586128082..30550f55a2 100644 --- a/assets/common/abilities/shield/block.ron +++ b/assets/common/abilities/shield/block.ron @@ -3,5 +3,9 @@ BasicBlock( recover_duration: 0.1, max_angle: 90.0, block_strength: 0.8, + parry_window: ( + buildup: true, + recover: false, + ), energy_cost: 0.0, ) \ No newline at end of file diff --git a/assets/common/abilities/sword/parrying_combo.ron b/assets/common/abilities/sword/parrying_combo.ron index d311941ad4..853d35d562 100644 --- a/assets/common/abilities/sword/parrying_combo.ron +++ b/assets/common/abilities/sword/parrying_combo.ron @@ -1,25 +1,64 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, - swing_duration: 0.1, - recover_duration: 0.2, - melee_constructor: ( - kind: Stab( - damage: 10, - poise: 0, - knockback: 0, - energy_regen: 0, +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 7, + poise: 0, + knockback: 0, + energy_regen: 5, + ), + range: 3.0, + angle: 45.0, + ), + buildup_duration: 0.3, + swing_duration: 0.1, + hit_timing: 0.5, + recover_duration: 0.5, + ori_modifier: 0.6, ), - range: 5.0, - angle: 10.0, - ), - ori_modifier: 1.0, + ( + melee_constructor: ( + kind: Slash( + damage: 10, + poise: 0, + knockback: 0, + energy_regen: 10, + ), + range: 3.0, + angle: 45.0, + ), + buildup_duration: 0.3, + swing_duration: 0.1, + hit_timing: 0.5, + recover_duration: 0.3, + ori_modifier: 0.6, + ), + ( + melee_constructor: ( + kind: Slash( + damage: 8, + poise: 0, + knockback: 0, + energy_regen: 10, + ), + range: 3.0, + angle: 45.0, + ), + buildup_duration: 0.2, + swing_duration: 0.1, + hit_timing: 0.5, + recover_duration: 0.2, + ori_modifier: 0.6, + ), + ], + is_stance: true, + energy_cost_per_strike: 10, meta: ( - kind: Some(Sword(Balanced)), + kind: Some(Sword(Parrying)), capabilities: ( - // Block - bits: 0b00000010, + // Buildup auto parries melee attacks + bits: 0b00000100, ), ), -) +) \ No newline at end of file diff --git a/assets/common/abilities/sword/parrying_counter.ron b/assets/common/abilities/sword/parrying_counter.ron index d311941ad4..4ad1953732 100644 --- a/assets/common/abilities/sword/parrying_counter.ron +++ b/assets/common/abilities/sword/parrying_counter.ron @@ -1,25 +1,26 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, - swing_duration: 0.1, - recover_duration: 0.2, - melee_constructor: ( - kind: Stab( - damage: 10, - poise: 0, - knockback: 0, - energy_regen: 0, +ComboMelee2( + strikes: [ + ( + melee_constructor: ( + kind: Slash( + damage: 15, + poise: 0, + knockback: 0, + energy_regen: 10, + ), + range: 6.0, + angle: 5.0, + ), + buildup_duration: 0.05, + swing_duration: 0.1, + hit_timing: 0.6, + recover_duration: 0.9, + ori_modifier: 0.6, ), - range: 5.0, - angle: 10.0, - ), - ori_modifier: 1.0, + ], + is_stance: false, + energy_cost_per_strike: 15, meta: ( - kind: Some(Sword(Balanced)), - capabilities: ( - // Block - bits: 0b00000010, - ), + kind: Some(Sword(Parrying)), ), -) +) \ No newline at end of file diff --git a/assets/common/abilities/sword/parrying_parry.ron b/assets/common/abilities/sword/parrying_parry.ron index d311941ad4..204653fee5 100644 --- a/assets/common/abilities/sword/parrying_parry.ron +++ b/assets/common/abilities/sword/parrying_parry.ron @@ -1,25 +1,14 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, - swing_duration: 0.1, - recover_duration: 0.2, - melee_constructor: ( - kind: Stab( - damage: 10, - poise: 0, - knockback: 0, - energy_regen: 0, - ), - range: 5.0, - angle: 10.0, +BasicBlock( + buildup_duration: 1.0, + recover_duration: 0.5, + max_angle: 45.0, + block_strength: 0.8, + parry_window: ( + buildup: true, + recover: true, ), - ori_modifier: 1.0, + energy_cost: 10.0, meta: ( - kind: Some(Sword(Balanced)), - capabilities: ( - // Block - bits: 0b00000010, - ), + kind: Some(Sword(Parrying)), ), -) +) \ No newline at end of file diff --git a/assets/common/abilities/sword/parrying_riposte.ron b/assets/common/abilities/sword/parrying_riposte.ron index d311941ad4..b27b26fff5 100644 --- a/assets/common/abilities/sword/parrying_riposte.ron +++ b/assets/common/abilities/sword/parrying_riposte.ron @@ -1,25 +1,19 @@ -// TODO: Make actual ability, just for testing right now -BasicMelee( - energy_cost: 50, - buildup_duration: 0.3, +RiposteMelee( + energy_cost: 0, + buildup_duration: 0.5, swing_duration: 0.1, - recover_duration: 0.2, + recover_duration: 0.3, melee_constructor: ( - kind: Stab( + kind: Slash( damage: 10, poise: 0, knockback: 0, energy_regen: 0, ), - range: 5.0, - angle: 10.0, + range: 4.0, + angle: 20.0, ), - ori_modifier: 1.0, meta: ( - kind: Some(Sword(Balanced)), - capabilities: ( - // Block - bits: 0b00000010, - ), - ), -) + kind: Some(Sword(Parrying)), + ) +) \ No newline at end of file diff --git a/common/src/combat.rs b/common/src/combat.rs index 9d34c99613..5b18087aa7 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -12,7 +12,6 @@ use crate::{ }, event::ServerEvent, outcome::Outcome, - states::utils::StageSection, uid::{Uid, UidAllocator}, util::Dir, }; @@ -129,6 +128,7 @@ impl Attack { pub fn effects(&self) -> impl Iterator { self.effects.iter() } pub fn compute_damage_reduction( + attacker: Option<&AttackerInfo>, target: &TargetInfo, source: AttackSource, dir: Dir, @@ -141,25 +141,28 @@ impl Attack { Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm); let block_reduction = match source { AttackSource::Melee => { - if let (Some(CharacterState::BasicBlock(data)), Some(ori)) = - (target.char_state, target.ori) - { - if ori.look_vec().angle_between(-*dir) < data.static_data.max_angle.to_radians() - { - let parry = matches!(data.stage_section, StageSection::Buildup); - emit_outcome(Outcome::Block { - parry, - pos: target.pos, - uid: target.uid, - }); - emit(ServerEvent::Parry { - entity: target.entity, - energy_cost: data.static_data.energy_cost, - }); - if parry { + if let (Some(char_state), Some(ori)) = (target.char_state, target.ori) { + if ori.look_vec().angle_between(-*dir) < char_state.block_angle() { + if char_state.is_parry() { + emit_outcome(Outcome::Block { + parry: true, + pos: target.pos, + uid: target.uid, + }); + emit(ServerEvent::ParryHook { + defender: target.entity, + attacker: attacker.map(|a| a.entity), + }); 1.0 + } else if let Some(block_strength) = char_state.block_strength() { + emit_outcome(Outcome::Block { + parry: false, + pos: target.pos, + uid: target.uid, + }); + block_strength } else { - data.static_data.block_strength + 0.0 } } else { 0.0 @@ -220,6 +223,7 @@ impl Attack { { is_applied = true; let damage_reduction = Attack::compute_damage_reduction( + attacker.as_ref(), &target, attack_source, dir, diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index 280080f831..10c088014e 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -407,7 +407,8 @@ impl From<&CharacterState> for CharacterAbilityType { | CharacterState::Wallrun(_) | CharacterState::ComboMelee2(_) | CharacterState::FinisherMelee(_) - | CharacterState::DiveMelee(_) => Self::Other, + | CharacterState::DiveMelee(_) + | CharacterState::RiposteMelee(_) => Self::Other, } } } @@ -479,6 +480,7 @@ pub enum CharacterAbility { recover_duration: f32, max_angle: f32, block_strength: f32, + parry_window: basic_block::ParryWindow, energy_cost: f32, #[serde(default)] meta: AbilityMeta, @@ -686,6 +688,15 @@ pub enum CharacterAbility { #[serde(default)] meta: AbilityMeta, }, + RiposteMelee { + energy_cost: f32, + buildup_duration: f32, + swing_duration: f32, + recover_duration: f32, + melee_constructor: MeleeConstructor, + #[serde(default)] + meta: AbilityMeta, + }, } impl Default for CharacterAbility { @@ -738,7 +749,8 @@ impl CharacterAbility { | CharacterAbility::ChargedMelee { energy_cost, .. } | CharacterAbility::Shockwave { energy_cost, .. } | CharacterAbility::BasicBlock { energy_cost, .. } - | CharacterAbility::SelfBuff { energy_cost, .. } => { + | CharacterAbility::SelfBuff { energy_cost, .. } + | CharacterAbility::RiposteMelee { energy_cost, .. } => { update.energy.try_change_by(-*energy_cost).is_ok() }, // Consumes energy within state, so value only checked before entering state @@ -817,6 +829,10 @@ impl CharacterAbility { recover_duration: 0.2, max_angle: 60.0, block_strength: 0.5, + parry_window: basic_block::ParryWindow { + buildup: true, + recover: false, + }, energy_cost: 2.5, meta: Default::default(), } @@ -916,6 +932,7 @@ impl CharacterAbility { max_angle: _, // Block strength explicitly not modified by power, that will be a separate stat block_strength: _, + parry_window: _, ref mut energy_cost, meta: _, } => { @@ -1240,6 +1257,20 @@ impl CharacterAbility { *energy_cost /= stats.energy_efficiency; *melee_constructor = melee_constructor.adjusted_by_stats(stats); }, + RiposteMelee { + ref mut energy_cost, + ref mut buildup_duration, + ref mut swing_duration, + ref mut recover_duration, + ref mut melee_constructor, + meta: _, + } => { + *buildup_duration /= stats.speed; + *swing_duration /= stats.speed; + *recover_duration /= stats.speed; + *energy_cost /= stats.energy_efficiency; + *melee_constructor = melee_constructor.adjusted_by_stats(stats); + }, } self } @@ -1265,7 +1296,8 @@ impl CharacterAbility { energy_cost_per_strike: energy_cost, .. } - | DiveMelee { energy_cost, .. } => *energy_cost, + | DiveMelee { energy_cost, .. } + | RiposteMelee { energy_cost, .. } => *energy_cost, BasicBeam { energy_drain, .. } => { if *energy_drain > f32::EPSILON { 1.0 @@ -1308,7 +1340,8 @@ impl CharacterAbility { | SpriteSummon { meta, .. } | FinisherMelee { meta, .. } | Music { meta, .. } - | DiveMelee { meta, .. } => *meta, + | DiveMelee { meta, .. } + | RiposteMelee { meta, .. } => *meta, } } @@ -1321,10 +1354,20 @@ impl CharacterAbility { const ENERGY_REDUCTION: f32 = 0.75; use CharacterAbility::*; match &mut self { - BasicMelee { energy_cost, .. } => { - *energy_cost *= ENERGY_REDUCTION; - }, - FinisherMelee { energy_cost, .. } => { + BasicMelee { energy_cost, .. } + | ComboMelee2 { + energy_cost_per_strike: energy_cost, + .. + } + | FinisherMelee { energy_cost, .. } + | DashMelee { energy_cost, .. } + | SpinMelee { energy_cost, .. } + | ChargedMelee { energy_cost, .. } + | Shockwave { energy_cost, .. } + | BasicBlock { energy_cost, .. } + | SelfBuff { energy_cost, .. } + | DiveMelee { energy_cost, .. } + | RiposteMelee { energy_cost, .. } => { *energy_cost *= ENERGY_REDUCTION; }, _ => {}, @@ -1953,6 +1996,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { recover_duration, max_angle, block_strength, + parry_window, energy_cost, meta: _, } => CharacterState::BasicBlock(basic_block::Data { @@ -1961,6 +2005,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { recover_duration: Duration::from_secs_f32(*recover_duration), max_angle: *max_angle, block_strength: *block_strength, + parry_window: *parry_window, energy_cost: *energy_cost, ability_info, }, @@ -2441,6 +2486,25 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { stage_section: StageSection::Movement, exhausted: false, }), + CharacterAbility::RiposteMelee { + energy_cost: _, + buildup_duration, + swing_duration, + recover_duration, + melee_constructor, + meta: _, + } => CharacterState::RiposteMelee(riposte_melee::Data { + static_data: riposte_melee::StaticData { + buildup_duration: Duration::from_secs_f32(*buildup_duration), + swing_duration: Duration::from_secs_f32(*swing_duration), + recover_duration: Duration::from_secs_f32(*recover_duration), + melee_constructor: *melee_constructor, + ability_info, + }, + timer: Duration::default(), + stage_section: StageSection::Buildup, + exhausted: false, + }), } } } @@ -2479,5 +2543,6 @@ bitflags::bitflags! { pub struct Capability: u8 { const ROLL_INTERRUPT = 0b00000001; const BLOCK_INTERRUPT = 0b00000010; + const BUILDUP_PARRIES = 0b00000100; } } diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index d57899cbc0..8f30484fb0 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -1,7 +1,7 @@ use crate::{ comp::{ - inventory::item::armor::Friction, item::ConsumableKind, ControlAction, Density, Energy, - InputAttr, InputKind, Ori, Pos, Vel, + ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction, Density, Energy, InputAttr, + InputKind, Ori, Pos, Vel, }, event::{LocalEvent, ServerEvent}, states::{ @@ -134,6 +134,8 @@ pub enum CharacterState { /// State entered when diving, melee attack triggered upon landing on the /// ground DiveMelee(dive_melee::Data), + /// Attack that attempts to parry, and if it parries moves to an attack + RiposteMelee(riposte_melee::Data), } impl CharacterState { @@ -170,6 +172,7 @@ impl CharacterState { }) | CharacterState::FinisherMelee(_) | CharacterState::DiveMelee(_) + | CharacterState::RiposteMelee(_) ) } @@ -211,6 +214,7 @@ impl CharacterState { | CharacterState::SpriteSummon(_) | CharacterState::FinisherMelee(_) | CharacterState::DiveMelee(_) + | CharacterState::RiposteMelee(_) ) } @@ -235,6 +239,7 @@ impl CharacterState { | CharacterState::Talk | CharacterState::FinisherMelee(_) | CharacterState::DiveMelee(_) + | CharacterState::RiposteMelee(_) ) } @@ -251,7 +256,45 @@ impl CharacterState { ) } - pub fn is_block(&self) -> bool { matches!(self, CharacterState::BasicBlock(_)) } + pub fn block_strength(&self) -> Option { + let strength = match self { + CharacterState::BasicBlock(c) => c.static_data.block_strength, + _ => return None, + }; + Some(strength) + } + + pub fn is_parry(&self) -> bool { + let from_capability = + if let Some(capabilities) = self.ability_info().map(|a| a.ability_meta.capabilities) { + capabilities.contains(Capability::BUILDUP_PARRIES) + && matches!(self.stage_section(), Some(StageSection::Buildup)) + } else { + false + }; + let from_state = match self { + CharacterState::BasicBlock(c) => c.is_parry(), + CharacterState::RiposteMelee(c) => matches!(c.stage_section, StageSection::Buildup), + _ => false, + }; + from_capability || from_state + } + + /// In radians + pub fn block_angle(&self) -> f32 { + match self { + CharacterState::BasicBlock(c) => c.static_data.max_angle.to_radians(), + CharacterState::ComboMelee2(c) => { + let strike_data = + c.static_data.strikes[c.completed_strikes % c.static_data.strikes.len()]; + strike_data.melee_constructor.angle.to_radians() + }, + CharacterState::RiposteMelee(c) => c.static_data.melee_constructor.angle.to_radians(), + // TODO: Add more here as needed, maybe look into having character state return the + // melee constructor if it has one and using that? + _ => 0.0, + } + } pub fn is_dodge(&self) -> bool { matches!(self, CharacterState::Roll(_)) } @@ -302,6 +345,7 @@ impl CharacterState { | CharacterState::UseItem(_) | CharacterState::SpriteInteract(_) | CharacterState::Music(_) + | CharacterState::RiposteMelee(_) ) } @@ -367,6 +411,7 @@ impl CharacterState { CharacterState::Music(data) => data.behavior(j, output_events), CharacterState::FinisherMelee(data) => data.behavior(j, output_events), CharacterState::DiveMelee(data) => data.behavior(j, output_events), + CharacterState::RiposteMelee(data) => data.behavior(j, output_events), } } @@ -418,6 +463,7 @@ impl CharacterState { CharacterState::Music(data) => data.handle_event(j, output_events, action), CharacterState::FinisherMelee(data) => data.handle_event(j, output_events, action), CharacterState::DiveMelee(data) => data.handle_event(j, output_events, action), + CharacterState::RiposteMelee(data) => data.handle_event(j, output_events, action), } } @@ -468,6 +514,7 @@ impl CharacterState { CharacterState::FinisherMelee(data) => Some(data.static_data.ability_info), CharacterState::Music(data) => Some(data.static_data.ability_info), CharacterState::DiveMelee(data) => Some(data.static_data.ability_info), + CharacterState::RiposteMelee(data) => Some(data.static_data.ability_info), } } @@ -510,6 +557,7 @@ impl CharacterState { CharacterState::FinisherMelee(data) => Some(data.stage_section), CharacterState::Music(data) => Some(data.stage_section), CharacterState::DiveMelee(data) => Some(data.stage_section), + CharacterState::RiposteMelee(data) => Some(data.stage_section), } } } diff --git a/common/src/event.rs b/common/src/event.rs index 73511ec6f6..3a945d19ba 100644 --- a/common/src/event.rs +++ b/common/src/event.rs @@ -179,9 +179,9 @@ pub enum ServerEvent { entity: EcsEntity, change: i32, }, - Parry { - entity: EcsEntity, - energy_cost: f32, + ParryHook { + defender: EcsEntity, + attacker: Option, }, RequestSiteInfo { entity: EcsEntity, diff --git a/common/src/states/basic_block.rs b/common/src/states/basic_block.rs index bed6dac9c6..0a0ceb54a1 100644 --- a/common/src/states/basic_block.rs +++ b/common/src/states/basic_block.rs @@ -9,6 +9,12 @@ use crate::{ use serde::{Deserialize, Serialize}; use std::time::Duration; +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct ParryWindow { + pub buildup: bool, + pub recover: bool, +} + /// Separated out to condense update portions of character state #[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] pub struct StaticData { @@ -20,6 +26,8 @@ pub struct StaticData { pub max_angle: f32, /// What percentage incoming damage is reduced by pub block_strength: f32, + /// What durations are considered a parry + pub parry_window: ParryWindow, /// What key is used to press ability pub ability_info: AbilityInfo, /// Energy consumed to initiate the block @@ -102,3 +110,13 @@ impl CharacterBehavior for Data { update } } + +impl Data { + pub fn is_parry(&self) -> bool { + match self.stage_section { + StageSection::Buildup => self.static_data.parry_window.buildup, + StageSection::Recover => self.static_data.parry_window.recover, + _ => false, + } + } +} diff --git a/common/src/states/finisher_melee.rs b/common/src/states/finisher_melee.rs index 5a4f33d607..5d3b7e779b 100644 --- a/common/src/states/finisher_melee.rs +++ b/common/src/states/finisher_melee.rs @@ -72,7 +72,6 @@ impl CharacterBehavior for Data { StageSection::Action => { if !self.exhausted { if let CharacterState::FinisherMelee(c) = &mut update.character { - c.timer = Duration::default(); c.exhausted = true; } diff --git a/common/src/states/mod.rs b/common/src/states/mod.rs index 0ad22e8356..8dd65a152d 100644 --- a/common/src/states/mod.rs +++ b/common/src/states/mod.rs @@ -23,6 +23,7 @@ pub mod idle; pub mod leap_melee; pub mod music; pub mod repeater_ranged; +pub mod riposte_melee; pub mod roll; pub mod self_buff; pub mod shockwave; diff --git a/common/src/states/riposte_melee.rs b/common/src/states/riposte_melee.rs new file mode 100644 index 0000000000..716dcd4192 --- /dev/null +++ b/common/src/states/riposte_melee.rs @@ -0,0 +1,115 @@ +use crate::{ + comp::{character_state::OutputEvents, CharacterState, Melee, MeleeConstructor, StateUpdate}, + states::{ + behavior::{CharacterBehavior, JoinData}, + utils::*, + wielding, + }, +}; +use serde::{Deserialize, Serialize}; +use std::time::Duration; + +/// Separated out to condense update portions of character state +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct StaticData { + /// How long until state should deal damage + pub buildup_duration: Duration, + /// How long the state is swinging for + pub swing_duration: Duration, + /// How long the state has until exiting + pub recover_duration: Duration, + /// Used to construct the Melee attack + pub melee_constructor: MeleeConstructor, + /// What key is used to press ability + pub ability_info: AbilityInfo, +} + +#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)] +pub struct Data { + /// Struct containing data that does not change over the course of the + /// character state + pub static_data: StaticData, + /// Timer for each stage + pub timer: Duration, + /// What section the character stage is in + pub stage_section: StageSection, + /// Whether the attack can deal more damage + pub exhausted: bool, +} + +impl CharacterBehavior for Data { + fn behavior(&self, data: &JoinData, output_events: &mut OutputEvents) -> StateUpdate { + let mut update = StateUpdate::from(data); + + handle_orientation(data, &mut update, 1.0, None); + handle_move(data, &mut update, 0.7); + handle_jump(data, output_events, &mut update, 1.0); + handle_interrupts(data, &mut update, None); + + match self.stage_section { + StageSection::Buildup => { + if self.timer < self.static_data.buildup_duration { + // Build up + if let CharacterState::RiposteMelee(c) = &mut update.character { + c.timer = tick_attack_or_default(data, self.timer, None); + } + } else { + // If duration finishes with no pary occurring, end character state + // Transition to action happens in parry hook server event + update.character = + CharacterState::Wielding(wielding::Data { is_sneaking: false }); + } + }, + StageSection::Action => { + if !self.exhausted { + if let CharacterState::RiposteMelee(c) = &mut update.character { + c.exhausted = true; + } + + let crit_data = get_crit_data(data, self.static_data.ability_info); + let buff_strength = get_buff_strength(data, self.static_data.ability_info); + + data.updater.insert( + data.entity, + self.static_data + .melee_constructor + .create_melee(crit_data, buff_strength), + ); + } else if self.timer < self.static_data.swing_duration { + // Swings + if let CharacterState::RiposteMelee(c) = &mut update.character { + c.timer = tick_attack_or_default(data, self.timer, None); + } + } else { + // Transitions to recover section of stage + if let CharacterState::RiposteMelee(c) = &mut update.character { + c.timer = Duration::default(); + c.stage_section = StageSection::Recover + } + } + }, + StageSection::Recover => { + if self.timer < self.static_data.recover_duration { + // Recovery + if let CharacterState::RiposteMelee(c) = &mut update.character { + c.timer = tick_attack_or_default(data, self.timer, None); + } + } else { + // Done + update.character = + CharacterState::Wielding(wielding::Data { is_sneaking: false }); + // Make sure attack component is removed + data.updater.remove::(data.entity); + } + }, + _ => { + // If it somehow ends up in an incorrect stage section + update.character = CharacterState::Wielding(wielding::Data { is_sneaking: false }); + // Make sure attack component is removed + data.updater.remove::(data.entity); + }, + } + + update + } +} diff --git a/common/systems/src/stats.rs b/common/systems/src/stats.rs index 6110c9b194..311681b476 100644 --- a/common/systems/src/stats.rs +++ b/common/systems/src/stats.rs @@ -198,7 +198,8 @@ impl<'a> System<'a> for Sys { | CharacterState::SelfBuff(_) | CharacterState::SpriteSummon(_) | CharacterState::FinisherMelee(_) - | CharacterState::DiveMelee(_) => { + | CharacterState::DiveMelee(_) + | CharacterState::RiposteMelee(_) => { if energy.regen_rate != 0.0 { energy.regen_rate = 0.0 } diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index b1b63dc39b..53f6b3f1d8 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -26,6 +26,7 @@ use common::{ outcome::{HealthChangeInfo, Outcome}, resources::Time, rtsim::RtSimEntity, + states::utils::StageSection, terrain::{Block, BlockKind, TerrainGrid}, uid::{Uid, UidAllocator}, util::Dir, @@ -41,7 +42,7 @@ use rand_distr::Distribution; use specs::{ join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity, Entity, WorldExt, }; -use std::{collections::HashMap, iter}; +use std::{collections::HashMap, iter, time::Duration}; use tracing::{debug, error}; use vek::{Vec2, Vec3}; @@ -1217,15 +1218,35 @@ pub fn handle_combo_change(server: &Server, entity: EcsEntity, change: i32) { } } -pub fn handle_parry(server: &Server, entity: EcsEntity, energy_cost: f32) { +pub fn handle_parry_hook(server: &Server, defender: EcsEntity, _attacker: Option) { let ecs = &server.state.ecs(); - if let Some(mut character) = ecs.write_storage::().get_mut(entity) { - *character = - CharacterState::Wielding(common::states::wielding::Data { is_sneaking: false }); + // Reset character state of defender + if let Some(mut char_state) = ecs + .write_storage::() + .get_mut(defender) + { + match &mut *char_state { + // If in combo melee and a stance, reset to stance mode + CharacterState::ComboMelee2(c) if c.static_data.is_stance => { + c.stage_section = None; + c.timer = Duration::default(); + }, + CharacterState::RiposteMelee(c) => { + c.stage_section = StageSection::Action; + c.timer = Duration::default(); + }, + char_state => { + *char_state = + CharacterState::Wielding(common::states::wielding::Data { is_sneaking: false }); + }, + } }; - if let Some(mut energy) = ecs.write_storage::().get_mut(entity) { - energy.change_by(energy_cost); + // Reward some energy to defender for successful parry + if let Some(mut energy) = ecs.write_storage::().get_mut(defender) { + const PARRY_REWARD: f32 = 5.0; + energy.change_by(PARRY_REWARD); } + // TODO: Some penalties for attacker } pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_range: Option) { diff --git a/server/src/events/mod.rs b/server/src/events/mod.rs index 3f6b9c3c60..d99711d3df 100644 --- a/server/src/events/mod.rs +++ b/server/src/events/mod.rs @@ -12,8 +12,9 @@ use entity_creation::{ use entity_manipulation::{ handle_aura, handle_bonk, handle_buff, handle_change_ability, handle_combo_change, handle_delete, handle_destroy, handle_energy_change, handle_entity_attacked_hook, - handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, handle_parry, - handle_poise, handle_reset_melee, handle_respawn, handle_teleport_to, handle_update_map_marker, + handle_explosion, handle_health_change, handle_knockback, handle_land_on_ground, + handle_parry_hook, handle_poise, handle_reset_melee, handle_respawn, handle_teleport_to, + handle_update_map_marker, }; use group_manip::handle_group; use information::handle_site_info; @@ -251,10 +252,9 @@ impl Server { ServerEvent::ComboChange { entity, change } => { handle_combo_change(self, entity, change) }, - ServerEvent::Parry { - entity, - energy_cost, - } => handle_parry(self, entity, energy_cost), + ServerEvent::ParryHook { defender, attacker } => { + handle_parry_hook(self, defender, attacker) + }, ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(self, entity, id), ServerEvent::MineBlock { entity, pos, tool } => { handle_mine_block(self, entity, pos, tool) diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index ed5153528d..9122a650d0 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -143,7 +143,7 @@ impl CharacterCacheKey { // state. let are_tools_visible = !is_first_person || cs - .map(|cs| cs.is_attack() || cs.is_block() || cs.is_wield()) + .map(|cs| cs.is_attack() || cs.block_strength().is_some() || cs.is_wield()) // If there's no provided character state but we're still somehow in first person, // We currently assume there's no need to visually model tools. //