Defensive stance required abilities

This commit is contained in:
Sam 2022-12-15 22:37:05 -05:00
parent 69665b5418
commit c3026d388a
15 changed files with 162 additions and 145 deletions

View File

@ -9,4 +9,12 @@ BasicBlock(
),
energy_cost: 0.0,
can_hold: true,
blocked_attacks: (
melee: true,
projectiles: true,
beams: true,
ground_shockwaves: false,
air_shockwaves: true,
explosions: true,
),
)

View File

@ -5,5 +5,5 @@ SelfBuff(
buff_kind: Hastened,
buff_strength: 0.25,
buff_duration: Some(30.0),
energy_cost: 25,
energy_cost: 30,
)

View File

@ -1,39 +1,20 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 5,
poise: 0,
knockback: 0,
energy_regen: 5,
),
range: 3.0,
angle: 45.0,
),
buildup_duration: 0.15,
swing_duration: 0.05,
hit_timing: 0.5,
recover_duration: 0.1,
ori_modifier: 0.6,
),
(
melee_constructor: (
kind: Slash(
damage: 10,
poise: 0,
knockback: 0,
energy_regen: 7.5,
),
range: 3.0,
angle: 45.0,
),
buildup_duration: 0.1,
swing_duration: 0.1,
hit_timing: 0.5,
recover_duration: 0.2,
ori_modifier: 0.6,
),
],
energy_cost_per_strike: 0,
BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.15,
max_angle: 45.0,
block_strength: 0.75,
parry_window: (
buildup: true,
recover: true,
),
energy_cost: 10,
can_hold: false,
blocked_attacks: (
melee: true,
projectiles: true,
beams: false,
ground_shockwaves: false,
air_shockwaves: false,
explosions: false,
),
)

View File

@ -9,4 +9,12 @@ BasicBlock(
),
energy_cost: 0,
can_hold: true,
blocked_attacks: (
melee: true,
projectiles: false,
beams: false,
ground_shockwaves: false,
air_shockwaves: false,
explosions: false,
),
)

View File

@ -1,39 +1,9 @@
ComboMelee2(
strikes: [
(
melee_constructor: (
kind: Slash(
damage: 5,
poise: 0,
knockback: 0,
energy_regen: 5,
),
range: 3.0,
angle: 45.0,
),
buildup_duration: 0.15,
swing_duration: 0.05,
hit_timing: 0.5,
recover_duration: 0.1,
ori_modifier: 0.6,
),
(
melee_constructor: (
kind: Slash(
damage: 10,
poise: 0,
knockback: 0,
energy_regen: 7.5,
),
range: 3.0,
angle: 45.0,
),
buildup_duration: 0.1,
swing_duration: 0.1,
hit_timing: 0.5,
recover_duration: 0.2,
ori_modifier: 0.6,
),
],
energy_cost_per_strike: 0,
)
SelfBuff(
buildup_duration: 0.25,
cast_duration: 0.3,
recover_duration: 0.25,
buff_kind: ProtectingWard,
buff_strength: 0.4,
buff_duration: Some(30.0),
energy_cost: 30,
)

View File

@ -43,7 +43,8 @@ pub enum AttackSource {
Melee,
Projectile,
Beam,
Shockwave,
GroundShockwave,
AirShockwave,
Explosion,
}
@ -145,40 +146,36 @@ impl Attack {
if damage.value > 0.0 {
let damage_reduction =
Damage::compute_damage_reduction(Some(damage), target.inventory, target.stats, msm);
let block_reduction = match source {
AttackSource::Melee => {
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 {
0.0
}
let block_reduction =
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(source) {
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(source) {
emit_outcome(Outcome::Block {
parry: false,
pos: target.pos,
uid: target.uid,
});
block_strength
} else {
0.0
}
} else {
0.0
}
},
_ => 0.0,
};
} else {
0.0
};
1.0 - (1.0 - damage_reduction) * (1.0 - block_reduction)
} else {
0.0
@ -867,7 +864,7 @@ impl From<AttackSource> for DamageSource {
AttackSource::Melee => DamageSource::Melee,
AttackSource::Projectile => DamageSource::Projectile,
AttackSource::Explosion => DamageSource::Explosion,
AttackSource::Shockwave => DamageSource::Shockwave,
AttackSource::AirShockwave | AttackSource::GroundShockwave => DamageSource::Shockwave,
AttackSource::Beam => DamageSource::Energy,
}
}

View File

@ -3,7 +3,7 @@ use crate::{
combat::{self, CombatEffect, DamageKind, Knockback},
comp::{
self, aura, beam, buff,
character_state::AttackImmunities,
character_state::AttackFilters,
inventory::{
item::{
tool::{AbilityContext, AbilityKind, Stats, ToolKind},
@ -547,6 +547,7 @@ pub enum CharacterAbility {
parry_window: basic_block::ParryWindow,
energy_cost: f32,
can_hold: bool,
blocked_attacks: AttackFilters,
#[serde(default)]
meta: AbilityMeta,
},
@ -556,7 +557,7 @@ pub enum CharacterAbility {
movement_duration: f32,
recover_duration: f32,
roll_strength: f32,
attack_immunities: AttackImmunities,
attack_immunities: AttackFilters,
#[serde(default)]
meta: AbilityMeta,
},
@ -922,7 +923,7 @@ impl CharacterAbility {
movement_duration: 0.33,
recover_duration: 0.125,
roll_strength: 3.0,
attack_immunities: AttackImmunities {
attack_immunities: AttackFilters {
melee: true,
projectiles: false,
beams: true,
@ -946,6 +947,14 @@ impl CharacterAbility {
},
energy_cost: 2.5,
can_hold: true,
blocked_attacks: AttackFilters {
melee: true,
projectiles: false,
ground_shockwaves: false,
air_shockwaves: false,
beams: false,
explosions: false,
},
meta: Default::default(),
}
}
@ -1049,6 +1058,7 @@ impl CharacterAbility {
parry_window: _,
ref mut energy_cost,
can_hold: _,
blocked_attacks: _,
meta: _,
} => {
*buildup_duration /= stats.speed;
@ -2216,6 +2226,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
parry_window,
energy_cost,
can_hold,
blocked_attacks,
meta: _,
} => CharacterState::BasicBlock(basic_block::Data {
static_data: basic_block::StaticData {
@ -2226,6 +2237,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
parry_window: *parry_window,
energy_cost: *energy_cost,
can_hold: *can_hold,
blocked_attacks: *blocked_attacks,
ability_info,
},
timer: Duration::default(),

View File

@ -1,4 +1,5 @@
use crate::{
combat::AttackSource,
comp::{
ability::Capability, inventory::item::armor::Friction, item::ConsumableKind, ControlAction,
Density, Energy, InputAttr, InputKind, Ori, Pos, Vel,
@ -279,20 +280,28 @@ impl CharacterState {
)
}
pub fn block_strength(&self) -> Option<f32> {
let from_capability = if let Some(capabilities) = self
.ability_info()
.map(|a| a.ability_meta)
.map(|m| m.capabilities)
{
(capabilities.contains(Capability::BUILDUP_BLOCKS)
&& matches!(self.stage_section(), Some(StageSection::Buildup)))
.then_some(0.5)
pub fn block_strength(&self, attack_source: AttackSource) -> Option<f32> {
let from_capability = if let AttackSource::Melee = attack_source {
if let Some(capabilities) = self
.ability_info()
.map(|a| a.ability_meta)
.map(|m| m.capabilities)
{
(capabilities.contains(Capability::BUILDUP_BLOCKS)
&& matches!(self.stage_section(), Some(StageSection::Buildup)))
.then_some(0.5)
} else {
None
}
} else {
None
};
let from_state = match self {
CharacterState::BasicBlock(c) => Some(c.static_data.block_strength),
CharacterState::BasicBlock(c) => c
.static_data
.blocked_attacks
.applies(attack_source)
.then_some(c.static_data.block_strength),
_ => None,
};
match (from_capability, from_state) {
@ -302,17 +311,21 @@ impl CharacterState {
}
}
pub fn is_parry(&self) -> bool {
let from_capability = self
.ability_info()
.map(|a| a.ability_meta.capabilities)
.map_or(false, |c| {
c.contains(Capability::BUILDUP_PARRIES)
&& matches!(self.stage_section(), Some(StageSection::Buildup))
});
pub fn is_parry(&self, attack_source: AttackSource) -> bool {
let melee = matches!(attack_source, AttackSource::Melee);
let from_capability = melee
&& self
.ability_info()
.map(|a| a.ability_meta.capabilities)
.map_or(false, |c| {
c.contains(Capability::BUILDUP_PARRIES)
&& matches!(self.stage_section(), Some(StageSection::Buildup))
});
let from_state = match self {
CharacterState::BasicBlock(c) => c.is_parry(),
CharacterState::RiposteMelee(c) => matches!(c.stage_section, StageSection::Buildup),
CharacterState::BasicBlock(c) => c.is_parry(attack_source),
CharacterState::RiposteMelee(c) => {
melee && matches!(c.stage_section, StageSection::Buildup)
},
_ => false,
};
from_capability || from_state
@ -342,7 +355,7 @@ impl CharacterState {
pub fn is_music(&self) -> bool { matches!(self, CharacterState::Music(_)) }
pub fn attack_immunities(&self) -> Option<AttackImmunities> {
pub fn attack_immunities(&self) -> Option<AttackFilters> {
if let CharacterState::Roll(c) = self {
Some(c.static_data.attack_immunities)
} else {
@ -879,7 +892,7 @@ pub struct DurationsInfo {
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize, Eq)]
pub struct AttackImmunities {
pub struct AttackFilters {
pub melee: bool,
pub projectiles: bool,
pub beams: bool,
@ -888,6 +901,19 @@ pub struct AttackImmunities {
pub explosions: bool,
}
impl AttackFilters {
pub fn applies(&self, attack: AttackSource) -> bool {
match attack {
AttackSource::Melee => self.melee,
AttackSource::Projectile => self.projectiles,
AttackSource::Beam => self.beams,
AttackSource::GroundShockwave => self.ground_shockwaves,
AttackSource::AirShockwave => self.air_shockwaves,
AttackSource::Explosion => self.explosions,
}
}
}
impl Default for CharacterState {
fn default() -> Self {
Self::Idle(idle::Data {

View File

@ -1,6 +1,10 @@
use super::utils::*;
use crate::{
comp::{character_state::OutputEvents, CharacterState, StateUpdate},
combat::AttackSource,
comp::{
character_state::{AttackFilters, OutputEvents},
CharacterState, StateUpdate,
},
states::behavior::{CharacterBehavior, JoinData},
};
use serde::{Deserialize, Serialize};
@ -31,6 +35,8 @@ pub struct StaticData {
pub energy_cost: f32,
/// Whether block can be held
pub can_hold: bool,
/// What kinds of attacks the block applies to
pub blocked_attacks: AttackFilters,
}
#[derive(Copy, Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -116,11 +122,13 @@ impl CharacterBehavior for Data {
}
impl Data {
pub fn is_parry(&self) -> bool {
match self.stage_section {
pub fn is_parry(&self, attack: AttackSource) -> bool {
let could_block = self.static_data.blocked_attacks.applies(attack);
let timed = match self.stage_section {
StageSection::Buildup => self.static_data.parry_window.buildup,
StageSection::Recover => self.static_data.parry_window.recover,
_ => false,
}
};
could_block && timed
}
}

View File

@ -1,7 +1,7 @@
use crate::{
comp::{
buff::{BuffChange, BuffKind},
character_state::{AttackImmunities, OutputEvents},
character_state::{AttackFilters, OutputEvents},
CharacterState, InputKind, StateUpdate,
},
event::ServerEvent,
@ -25,7 +25,7 @@ pub struct StaticData {
/// Affects the speed and distance of the roll
pub roll_strength: f32,
/// Affects whether you are immune to various attacks while rolling
pub attack_immunities: AttackImmunities,
pub attack_immunities: AttackFilters,
/// Information about the ability
pub ability_info: AbilityInfo,
}

View File

@ -245,7 +245,11 @@ impl<'a> System<'a> for Sys {
dir,
attack_options,
1.0,
AttackSource::Shockwave,
if shockwave.requires_ground {
AttackSource::GroundShockwave
} else {
AttackSource::AirShockwave
},
*read_data.time,
|e| server_emitter.emit(e),
|o| outcomes_emitter.emit(o),

View File

@ -259,7 +259,10 @@ impl Animation for BlockAnimation {
next.second = next.main;
}
},
Some("common.abilities.sword.defensive_parry") => {
Some(
"common.abilities.sword.defensive_parry"
| "common.abilities.sword.defensive_deflect",
) => {
let (move1, move2, move3) = match stage_section {
Some(StageSection::Buildup) => (anim_time.powi(2), 0.0, 0.0),
Some(StageSection::Action) => (1.0, (anim_time * 20.0).sin(), 0.0),

View File

@ -63,7 +63,7 @@ impl Animation for SelfBuffAnimation {
next.shorts.orientation.rotate_x(move2 * 0.2);
next.shorts.position += Vec3::new(0.0, move2 * 1.0, 0.0);
},
Some("common.abilities.sword.defensive_bulwark") => {
Some("common.abilities.sword.defensive_stalwart_sword") => {
let (move1, move2, move3) = match stage_section {
Some(StageSection::Movement) => (anim_time.powf(0.25), 0.0, 0.0),
Some(StageSection::Action) => (1.0, anim_time.powi(2), 0.0),

View File

@ -2,7 +2,7 @@ use super::*;
use crate::audio::sfx::SfxEvent;
use common::{
comp::{
bird_large, character_state::AttackImmunities, humanoid, quadruped_medium, quadruped_small,
bird_large, character_state::AttackFilters, humanoid, quadruped_medium, quadruped_small,
Body, CharacterState, Ori, PhysicsState,
},
states,
@ -184,7 +184,7 @@ fn maps_roll() {
movement_duration: Duration::default(),
recover_duration: Duration::default(),
roll_strength: 0.0,
attack_immunities: AttackImmunities {
attack_immunities: AttackFilters {
melee: false,
projectiles: false,
beams: false,

View File

@ -143,7 +143,7 @@ impl CharacterCacheKey {
// state.
let are_tools_visible = !is_first_person
|| cs
.map(|cs| cs.is_attack() || cs.block_strength().is_some() || cs.is_wield())
.map(|cs| cs.is_attack() || 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.
//