mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Parrying abilities
This commit is contained in:
parent
d99eddb483
commit
8a578bf3f6
@ -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,
|
||||
)
|
@ -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,
|
||||
),
|
||||
),
|
||||
)
|
||||
)
|
@ -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)),
|
||||
),
|
||||
)
|
||||
)
|
@ -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)),
|
||||
),
|
||||
)
|
||||
)
|
@ -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)),
|
||||
)
|
||||
)
|
@ -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<Item = &AttackEffect> { 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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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<f32> {
|
||||
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),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -179,9 +179,9 @@ pub enum ServerEvent {
|
||||
entity: EcsEntity,
|
||||
change: i32,
|
||||
},
|
||||
Parry {
|
||||
entity: EcsEntity,
|
||||
energy_cost: f32,
|
||||
ParryHook {
|
||||
defender: EcsEntity,
|
||||
attacker: Option<EcsEntity>,
|
||||
},
|
||||
RequestSiteInfo {
|
||||
entity: EcsEntity,
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
115
common/src/states/riposte_melee.rs
Normal file
115
common/src/states/riposte_melee.rs
Normal file
@ -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::<Melee>(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::<Melee>(data.entity);
|
||||
},
|
||||
}
|
||||
|
||||
update
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
|
@ -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<EcsEntity>) {
|
||||
let ecs = &server.state.ecs();
|
||||
if let Some(mut character) = ecs.write_storage::<CharacterState>().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::<comp::CharacterState>()
|
||||
.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::<Energy>().get_mut(entity) {
|
||||
energy.change_by(energy_cost);
|
||||
// Reward some energy to defender for successful parry
|
||||
if let Some(mut energy) = ecs.write_storage::<Energy>().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<f32>) {
|
||||
|
@ -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)
|
||||
|
@ -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.
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user