mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Defensive stance required abilities
This commit is contained in:
parent
69665b5418
commit
c3026d388a
@ -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,
|
||||
),
|
||||
)
|
@ -5,5 +5,5 @@ SelfBuff(
|
||||
buff_kind: Hastened,
|
||||
buff_strength: 0.25,
|
||||
buff_duration: Some(30.0),
|
||||
energy_cost: 25,
|
||||
energy_cost: 30,
|
||||
)
|
||||
|
@ -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,
|
||||
),
|
||||
)
|
@ -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,
|
||||
),
|
||||
)
|
@ -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,
|
||||
)
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
@ -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(),
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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),
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
//
|
||||
|
Loading…
Reference in New Issue
Block a user