Merge branch 'nerasw/defensive_stance_tweaks' into 'master'

Nerasw/defensive stance tweaks

See merge request veloren/veloren!4020
This commit is contained in:
Isse 2023-07-28 21:29:32 +00:00
commit 05e925d180
29 changed files with 267 additions and 100 deletions

View File

@ -8,10 +8,24 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [Unreleased] ## [Unreleased]
### Added ### Added
- Esperanto translation
- Item quantity sort in player inventory.
- Using Block('Alt' by default) in Defensive Stance now feels stronger
- Recipe for twigs from wooden logs - Recipe for twigs from wooden logs
- First version of multisalvage that allows to obtain more than one piece of material from salvage - First version of multisalvage that allows to obtain more than one piece of material from salvage
### Changed ### Changed
- Plugins now target wasm32-unknown-wasi and all wasm cfgs are gone
- Slightly reduced quantities of ingredients needed to craft cooked foods
- Improved and cleaned loot tables for T1 and T2 dungeons as well as large cave monsters (Good bye, Bowls and Stones!)
- Defensive Fell Strike's dmg raised
- Defensive Cascade's more effective against parried foes
- Defensive Riposte's buildup duration raised a bit
- Capabilities of strikes to parry & block now more reliable
- Defensive Disengage now more responsive and can block melee
- Deflect no longer parry melee hits
- Made helmets, necklaces, rings, twig armors and some gliders salvageable - Made helmets, necklaces, rings, twig armors and some gliders salvageable
- Tweaked stats on some foods so they generally increase a tiny bit more HP - Tweaked stats on some foods so they generally increase a tiny bit more HP
- Reduced idle time after consumption from 5 to 4 seconds - Reduced idle time after consumption from 5 to 4 seconds

View File

@ -3,6 +3,13 @@
// A set of abilities is a primary, a secondary, and a vec of all extra abilities // A set of abilities is a primary, a secondary, and a vec of all extra abilities
({ ({
Tool(Sword): ( Tool(Sword): (
guard: Some(Contextualized(
pseudo_id: "veloren.core.pseudo_abilities.sword.guard",
abilities: [
([Stance(Sword(Defensive))], (None, "common.abilities.sword.defensive_guard")),
([], (None, "common.abilities.sword.basic_guard")),
],
)),
primary: Contextualized( primary: Contextualized(
pseudo_id: "veloren.core.pseudo_abilities.sword.double_slash", pseudo_id: "veloren.core.pseudo_abilities.sword.double_slash",
abilities: [ abilities: [
@ -136,6 +143,7 @@
], ],
), ),
Tool(Axe): ( Tool(Axe): (
guard: Some(Simple(None, "common.abilities.axe.basic_guard")),
primary: Simple(None, "common.abilities.axe.doublestrike"), primary: Simple(None, "common.abilities.axe.doublestrike"),
secondary: Simple(None, "common.abilities.axe.spin"), secondary: Simple(None, "common.abilities.axe.spin"),
abilities: [ abilities: [
@ -143,6 +151,7 @@
], ],
), ),
Tool(Hammer): ( Tool(Hammer): (
guard: Some(Simple(None, "common.abilities.hammer.basic_guard")),
primary: Simple(None, "common.abilities.hammer.singlestrike"), primary: Simple(None, "common.abilities.hammer.singlestrike"),
secondary: Simple(None, "common.abilities.hammer.charged"), secondary: Simple(None, "common.abilities.hammer.charged"),
abilities: [ abilities: [

View File

@ -0,0 +1,21 @@
BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.2,
max_angle: 60.0,
block_strength: 0.5,
parry_window: (
buildup: true,
recover: false,
),
energy_cost: 5,
energy_regen: 2.5,
can_hold: true,
blocked_attacks: (
melee: true,
projectiles: false,
beams: false,
ground_shockwaves: false,
air_shockwaves: false,
explosions: false,
),
)

View File

@ -0,0 +1,21 @@
BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.2,
max_angle: 60.0,
block_strength: 0.5,
parry_window: (
buildup: true,
recover: false,
),
energy_cost: 5,
energy_regen: 2.5,
can_hold: true,
blocked_attacks: (
melee: true,
projectiles: false,
beams: false,
ground_shockwaves: false,
air_shockwaves: false,
explosions: false,
),
)

View File

@ -8,6 +8,7 @@ BasicBlock(
recover: false, recover: false,
), ),
energy_cost: 0.0, energy_cost: 0.0,
energy_regen: 0.0,
can_hold: true, can_hold: true,
blocked_attacks: ( blocked_attacks: (
melee: true, melee: true,

View File

@ -0,0 +1,21 @@
BasicBlock(
buildup_duration: 0.25,
recover_duration: 0.2,
max_angle: 60.0,
block_strength: 0.5,
parry_window: (
buildup: true,
recover: false,
),
energy_cost: 5,
energy_regen: 2.5,
can_hold: true,
blocked_attacks: (
melee: true,
projectiles: false,
beams: false,
ground_shockwaves: false,
air_shockwaves: false,
explosions: false,
),
)

View File

@ -10,7 +10,7 @@ ComboMelee2(
), ),
range: 4.0, range: 4.0,
angle: 15.0, angle: 15.0,
damage_effect: Some(BuffsVulnerable(0.5, Parried)), damage_effect: Some(BuffsVulnerable(0.6, Parried)),
), ),
buildup_duration: 0.4, buildup_duration: 0.4,
swing_duration: 0.1, swing_duration: 0.1,
@ -21,7 +21,7 @@ ComboMelee2(
], ],
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup portion // The ability will parry melee attacks in the buildup & swing portion
capabilities: ("BUILDUP_PARRIES"), capabilities: ("PARRIES"),
), ),
) )

View File

@ -21,7 +21,7 @@ ComboMelee2(
], ],
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup portion // The ability will parry melee attacks in the buildup & swing portion
capabilities: ("BUILDUP_PARRIES"), capabilities: ("PARRIES"),
), ),
) )

View File

@ -40,7 +40,7 @@ ComboMelee2(
energy_cost_per_strike: 2.5, energy_cost_per_strike: 2.5,
auto_progress: true, auto_progress: true,
meta: ( meta: (
// The ability will parry melee attacks in the buildup portion // The ability will parry melee attacks in the buildup & swing portion
capabilities: ("BUILDUP_PARRIES"), capabilities: ("PARRIES"),
), ),
) )

View File

@ -1,18 +1,19 @@
BasicBlock( BasicBlock(
buildup_duration: 0.25, buildup_duration: 0.4,
recover_duration: 0.15, recover_duration: 0.2,
max_angle: 45.0, max_angle: 45.0,
block_strength: 0.75, block_strength: 0.75,
parry_window: ( parry_window: (
buildup: true, buildup: true,
recover: true, recover: false,
), ),
energy_cost: 0, energy_cost: 2.5,
energy_regen: 17.5,
can_hold: false, can_hold: false,
blocked_attacks: ( blocked_attacks: (
melee: true, melee: false,
projectiles: true, projectiles: true,
beams: false, beams: true,
ground_shockwaves: false, ground_shockwaves: false,
air_shockwaves: false, air_shockwaves: false,
explosions: false, explosions: false,

View File

@ -11,13 +11,13 @@ ComboMelee2(
range: 6.0, range: 6.0,
angle: 45.0, angle: 45.0,
), ),
buildup_duration: 0.1, buildup_duration: 0.05,
swing_duration: 0.1, swing_duration: 0.1,
hit_timing: 0.6, hit_timing: 0.6,
recover_duration: 0.4, recover_duration: 0.3,
movement: ( movement: (
buildup: None, buildup: None,
swing: None, swing: Some(Reverse(2)),
recover: Some(Reverse(1.5)), recover: Some(Reverse(1.5)),
), ),
ori_modifier: 0.6, ori_modifier: 0.6,
@ -26,5 +26,6 @@ ComboMelee2(
energy_cost_per_strike: 0, energy_cost_per_strike: 0,
meta: ( meta: (
init_event: Some(EnterStance(Sword(Defensive))), init_event: Some(EnterStance(Sword(Defensive))),
capabilities: ("BLOCKS"),
), ),
) )

View File

@ -37,7 +37,7 @@ ComboMelee2(
], ],
energy_cost_per_strike: 0, energy_cost_per_strike: 0,
meta: ( meta: (
// Blocks melee attacks at 50% strength // Blocks melee attacks at 50% strength during buildup_duration & swing_duration
capabilities: ("BUILDUP_BLOCKS"), capabilities: ("BLOCKS"),
), ),
) )

View File

@ -22,7 +22,7 @@ ComboMelee2(
], ],
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup portion // The ability will parry melee attacks in the buildup & swing portion
capabilities: ("BUILDUP_PARRIES"), capabilities: ("PARRIES"),
), ),
) )

View File

@ -3,7 +3,7 @@ ComboMelee2(
( (
melee_constructor: ( melee_constructor: (
kind: Slash( kind: Slash(
damage: 15, damage: 20,
poise: 5, poise: 5,
knockback: 0, knockback: 0,
energy_regen: 0, energy_regen: 0,
@ -21,7 +21,7 @@ ComboMelee2(
], ],
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup portion // The ability will parry melee attacks in the buildup & swing portion
capabilities: ("BUILDUP_PARRIES"), capabilities: ("PARRIES"),
), ),
) )

View File

@ -0,0 +1,24 @@
BasicBlock(
buildup_duration: 0.4,
recover_duration: 0.15,
max_angle: 60.0,
block_strength: 0.75,
parry_window: (
buildup: true,
recover: false,
),
energy_cost: 2.5,
energy_regen: 17.5,
can_hold: true,
blocked_attacks: (
melee: true,
projectiles: false,
beams: false,
ground_shockwaves: false,
air_shockwaves: false,
explosions: false,
),
meta: (
requirements: (stance: Some(Sword(Defensive))),
),
)

View File

@ -1,6 +1,6 @@
RiposteMelee( RiposteMelee(
energy_cost: 5, energy_cost: 5,
buildup_duration: 0.3, buildup_duration: 0.4,
swing_duration: 0.1, swing_duration: 0.1,
recover_duration: 0.2, recover_duration: 0.2,
melee_constructor: ( melee_constructor: (

View File

@ -26,7 +26,7 @@ ComboMelee2(
], ],
energy_cost_per_strike: 5, energy_cost_per_strike: 5,
meta: ( meta: (
// The ability will parry melee attacks in the buildup portion // The ability will parry melee attacks in the buildup & swing portion
capabilities: ("BUILDUP_PARRIES"), capabilities: ("PARRIES"),
), ),
) )

View File

@ -9,7 +9,7 @@ ChargedMelee(
energy_regen: 0, energy_regen: 0,
), ),
scaled: Some(Stab( scaled: Some(Stab(
damage: 10, damage: 12,
poise: 5, poise: 5,
knockback: 0, knockback: 0,
energy_regen: 15, energy_regen: 15,

View File

@ -148,6 +148,7 @@ impl Attack {
emit(ServerEvent::ParryHook { emit(ServerEvent::ParryHook {
defender: target.entity, defender: target.entity,
attacker: attacker.map(|a| a.entity), attacker: attacker.map(|a| a.entity),
source,
}); });
1.0 1.0
} else if let Some(block_strength) = char_state.block_strength(source) { } else if let Some(block_strength) = char_state.block_strength(source) {

View File

@ -42,6 +42,7 @@ pub type AuxiliaryKey = (Option<ToolKind>, Option<ToolKind>);
// considerations. // considerations.
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct ActiveAbilities { pub struct ActiveAbilities {
pub guard: GuardAbility,
pub primary: PrimaryAbility, pub primary: PrimaryAbility,
pub secondary: SecondaryAbility, pub secondary: SecondaryAbility,
pub movement: MovementAbility, pub movement: MovementAbility,
@ -55,6 +56,7 @@ impl Component for ActiveAbilities {
impl Default for ActiveAbilities { impl Default for ActiveAbilities {
fn default() -> Self { fn default() -> Self {
Self { Self {
guard: GuardAbility::Tool,
primary: PrimaryAbility::Tool, primary: PrimaryAbility::Tool,
secondary: SecondaryAbility::Tool, secondary: SecondaryAbility::Tool,
movement: MovementAbility::Species, movement: MovementAbility::Species,
@ -123,6 +125,7 @@ impl ActiveAbilities {
skill_set: Option<&SkillSet>, skill_set: Option<&SkillSet>,
) -> Ability { ) -> Ability {
match input { match input {
AbilityInput::Guard => self.guard.into(),
AbilityInput::Primary => self.primary.into(), AbilityInput::Primary => self.primary.into(),
AbilityInput::Secondary => self.secondary.into(), AbilityInput::Secondary => self.secondary.into(),
AbilityInput::Movement => self.movement.into(), AbilityInput::Movement => self.movement.into(),
@ -165,6 +168,22 @@ impl ActiveAbilities {
}; };
match ability { match ability {
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| {
abilities
.guard(Some(skill_set), contexts)
.map(|a| a.ability.clone())
})
.map(|ability| (scale_ability(ability, EquipSlot::ActiveMainhand), true))
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand)
.and_then(|abilities| {
abilities
.secondary(Some(skill_set), contexts)
.map(|a| a.ability.clone())
})
.map(|ability| (scale_ability(ability, EquipSlot::ActiveOffhand), false))
}),
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand) Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| { .and_then(|abilities| {
abilities abilities
@ -250,6 +269,7 @@ impl ActiveAbilities {
} }
pub enum AbilityInput { pub enum AbilityInput {
Guard,
Primary, Primary,
Secondary, Secondary,
Movement, Movement,
@ -258,6 +278,7 @@ pub enum AbilityInput {
#[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)] #[derive(Copy, Clone, Serialize, Deserialize, Debug, PartialEq, Eq)]
pub enum Ability { pub enum Ability {
ToolGuard,
ToolPrimary, ToolPrimary,
ToolSecondary, ToolSecondary,
SpeciesMovement, SpeciesMovement,
@ -293,6 +314,31 @@ impl Ability {
}; };
match self { match self {
Ability::ToolGuard => ability_set(EquipSlot::ActiveMainhand)
.and_then(|abilities| {
abilities
.guard(skillset, contexts)
.map(|a| a.id.as_str())
.or_else(|| {
abilities
.guard
.as_ref()
.and_then(|g| contextual_id(Some(g)))
})
})
.or_else(|| {
ability_set(EquipSlot::ActiveOffhand).and_then(|abilities| {
abilities
.guard(skillset, contexts)
.map(|a| a.id.as_str())
.or_else(|| {
abilities
.guard
.as_ref()
.and_then(|g| contextual_id(Some(g)))
})
})
}),
Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| { Ability::ToolPrimary => ability_set(EquipSlot::ActiveMainhand).and_then(|abilities| {
abilities abilities
.primary(skillset, contexts) .primary(skillset, contexts)
@ -335,6 +381,20 @@ impl Ability {
} }
} }
} }
#[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub enum GuardAbility {
Tool,
Empty,
}
impl From<GuardAbility> for Ability {
fn from(guard: GuardAbility) -> Self {
match guard {
GuardAbility::Tool => Ability::ToolGuard,
GuardAbility::Empty => Ability::Empty,
}
}
}
#[derive(Copy, Clone, Serialize, Deserialize, Debug)] #[derive(Copy, Clone, Serialize, Deserialize, Debug)]
pub enum PrimaryAbility { pub enum PrimaryAbility {
@ -550,6 +610,7 @@ pub enum CharacterAbility {
block_strength: f32, block_strength: f32,
parry_window: basic_block::ParryWindow, parry_window: basic_block::ParryWindow,
energy_cost: f32, energy_cost: f32,
energy_regen: f32,
can_hold: bool, can_hold: bool,
blocked_attacks: AttackFilters, blocked_attacks: AttackFilters,
#[serde(default)] #[serde(default)]
@ -967,30 +1028,6 @@ impl CharacterAbility {
} }
} }
pub fn default_block() -> CharacterAbility {
CharacterAbility::BasicBlock {
buildup_duration: 0.25,
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,
can_hold: true,
blocked_attacks: AttackFilters {
melee: true,
projectiles: false,
ground_shockwaves: false,
air_shockwaves: false,
beams: false,
explosions: false,
},
meta: Default::default(),
}
}
#[must_use] #[must_use]
pub fn adjusted_by_stats(mut self, stats: Stats) -> Self { pub fn adjusted_by_stats(mut self, stats: Stats) -> Self {
use CharacterAbility::*; use CharacterAbility::*;
@ -1090,6 +1127,7 @@ impl CharacterAbility {
block_strength: _, block_strength: _,
parry_window: _, parry_window: _,
ref mut energy_cost, ref mut energy_cost,
energy_regen: _,
can_hold: _, can_hold: _,
blocked_attacks: _, blocked_attacks: _,
meta: _, meta: _,
@ -2247,6 +2285,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
block_strength, block_strength,
parry_window, parry_window,
energy_cost, energy_cost,
energy_regen,
can_hold, can_hold,
blocked_attacks, blocked_attacks,
meta: _, meta: _,
@ -2258,6 +2297,7 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState {
block_strength: *block_strength, block_strength: *block_strength,
parry_window: *parry_window, parry_window: *parry_window,
energy_cost: *energy_cost, energy_cost: *energy_cost,
energy_regen: *energy_regen,
can_hold: *can_hold, can_hold: *can_hold,
blocked_attacks: *blocked_attacks, blocked_attacks: *blocked_attacks,
ability_info, ability_info,
@ -2910,13 +2950,13 @@ bitflags::bitflags! {
// Allows blocking to interrupt the ability at any point // Allows blocking to interrupt the ability at any point
const BLOCK_INTERRUPT = 0b00000010; const BLOCK_INTERRUPT = 0b00000010;
// When the ability is in the buildup section, it counts as a block with 50% DR // When the ability is in the buildup section, it counts as a block with 50% DR
const BUILDUP_BLOCKS = 0b00000100; const BLOCKS = 0b00000100;
// When in the ability, an entity only receives half as much poise damage // When in the ability, an entity only receives half as much poise damage
const POISE_RESISTANT = 0b00001000; const POISE_RESISTANT = 0b00001000;
// WHen in the ability, an entity only receives half as much knockback // WHen in the ability, an entity only receives half as much knockback
const KNOCKBACK_RESISTANT = 0b00010000; const KNOCKBACK_RESISTANT = 0b00010000;
// The ability will parry melee attacks in the buildup portion // The ability will parry melee attacks in the buildup portion
const BUILDUP_PARRIES = 0b00100000; const PARRIES = 0b00100000;
} }
} }

View File

@ -289,8 +289,11 @@ impl CharacterState {
.map(|a| a.ability_meta) .map(|a| a.ability_meta)
.map(|m| m.capabilities) .map(|m| m.capabilities)
{ {
(capabilities.contains(Capability::BUILDUP_BLOCKS) (capabilities.contains(Capability::BLOCKS)
&& matches!(self.stage_section(), Some(StageSection::Buildup))) && matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
))
.then_some(0.5) .then_some(0.5)
} else { } else {
None None
@ -320,13 +323,20 @@ impl CharacterState {
.ability_info() .ability_info()
.map(|a| a.ability_meta.capabilities) .map(|a| a.ability_meta.capabilities)
.map_or(false, |c| { .map_or(false, |c| {
c.contains(Capability::BUILDUP_PARRIES) c.contains(Capability::PARRIES)
&& matches!(self.stage_section(), Some(StageSection::Buildup)) && matches!(
self.stage_section(),
Some(StageSection::Buildup | StageSection::Action)
)
}); });
let from_state = match self { let from_state = match self {
CharacterState::BasicBlock(c) => c.is_parry(attack_source), CharacterState::BasicBlock(c) => c.is_parry(attack_source),
CharacterState::RiposteMelee(c) => { CharacterState::RiposteMelee(c) => {
melee && matches!(c.stage_section, StageSection::Buildup) melee
&& matches!(
c.stage_section,
StageSection::Buildup | StageSection::Action
)
}, },
_ => false, _ => false,
}; };

View File

@ -215,11 +215,12 @@ impl From<InputKind> for Option<ability::AbilityInput> {
fn from(input: InputKind) -> Option<ability::AbilityInput> { fn from(input: InputKind) -> Option<ability::AbilityInput> {
use ability::AbilityInput; use ability::AbilityInput;
match input { match input {
InputKind::Block => Some(AbilityInput::Guard),
InputKind::Primary => Some(AbilityInput::Primary), InputKind::Primary => Some(AbilityInput::Primary),
InputKind::Secondary => Some(AbilityInput::Secondary), InputKind::Secondary => Some(AbilityInput::Secondary),
InputKind::Roll => Some(AbilityInput::Movement), InputKind::Roll => Some(AbilityInput::Movement),
InputKind::Ability(index) => Some(AbilityInput::Auxiliary(index)), InputKind::Ability(index) => Some(AbilityInput::Auxiliary(index)),
InputKind::Jump | InputKind::Fly | InputKind::Block => None, InputKind::Jump | InputKind::Fly => None,
} }
} }
} }

View File

@ -294,6 +294,7 @@ impl Tool {
#[derive(Clone, Debug, Serialize, Deserialize)] #[derive(Clone, Debug, Serialize, Deserialize)]
pub struct AbilitySet<T> { pub struct AbilitySet<T> {
pub guard: Option<AbilityKind<T>>,
pub primary: AbilityKind<T>, pub primary: AbilityKind<T>,
pub secondary: AbilityKind<T>, pub secondary: AbilityKind<T>,
pub abilities: Vec<AbilityKind<T>>, pub abilities: Vec<AbilityKind<T>>,
@ -422,6 +423,7 @@ impl AbilitySet<AbilityItem> {
impl<T> AbilitySet<T> { impl<T> AbilitySet<T> {
pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilitySet<U> { pub fn map<U, F: FnMut(T) -> U>(self, mut f: F) -> AbilitySet<U> {
AbilitySet { AbilitySet {
guard: self.guard.map(|g| g.map(&mut f)),
primary: self.primary.map(&mut f), primary: self.primary.map(&mut f),
secondary: self.secondary.map(&mut f), secondary: self.secondary.map(&mut f),
abilities: self.abilities.into_iter().map(|x| x.map(&mut f)).collect(), abilities: self.abilities.into_iter().map(|x| x.map(&mut f)).collect(),
@ -430,12 +432,19 @@ impl<T> AbilitySet<T> {
pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilitySet<U> { pub fn map_ref<U, F: FnMut(&T) -> U>(&self, mut f: F) -> AbilitySet<U> {
AbilitySet { AbilitySet {
guard: self.guard.as_ref().map(|g| g.map_ref(&mut f)),
primary: self.primary.map_ref(&mut f), primary: self.primary.map_ref(&mut f),
secondary: self.secondary.map_ref(&mut f), secondary: self.secondary.map_ref(&mut f),
abilities: self.abilities.iter().map(|x| x.map_ref(&mut f)).collect(), abilities: self.abilities.iter().map(|x| x.map_ref(&mut f)).collect(),
} }
} }
pub fn guard(&self, skillset: Option<&SkillSet>, contexts: &[AbilityContext]) -> Option<&T> {
self.guard
.as_ref()
.and_then(|g| g.ability(skillset, contexts))
}
pub fn primary(&self, skillset: Option<&SkillSet>, contexts: &[AbilityContext]) -> Option<&T> { pub fn primary(&self, skillset: Option<&SkillSet>, contexts: &[AbilityContext]) -> Option<&T> {
self.primary.ability(skillset, contexts) self.primary.ability(skillset, contexts)
} }
@ -463,6 +472,7 @@ impl<T> AbilitySet<T> {
impl Default for AbilitySet<AbilityItem> { impl Default for AbilitySet<AbilityItem> {
fn default() -> Self { fn default() -> Self {
AbilitySet { AbilitySet {
guard: None,
primary: AbilityKind::Simple(None, AbilityItem { primary: AbilityKind::Simple(None, AbilityItem {
id: String::new(), id: String::new(),
ability: CharacterAbility::default(), ability: CharacterAbility::default(),

View File

@ -1,5 +1,6 @@
use crate::{ use crate::{
character::CharacterId, character::CharacterId,
combat::AttackSource,
comp::{ comp::{
self, self,
agent::Sound, agent::Sound,
@ -261,6 +262,7 @@ pub enum ServerEvent {
ParryHook { ParryHook {
defender: EcsEntity, defender: EcsEntity,
attacker: Option<EcsEntity>, attacker: Option<EcsEntity>,
source: AttackSource,
}, },
RequestSiteInfo { RequestSiteInfo {
entity: EcsEntity, entity: EcsEntity,

View File

@ -33,6 +33,8 @@ pub struct StaticData {
pub ability_info: AbilityInfo, pub ability_info: AbilityInfo,
/// Energy consumed to initiate the block /// Energy consumed to initiate the block
pub energy_cost: f32, pub energy_cost: f32,
/// Energy recovered upon successful parry
pub energy_regen: f32,
/// Whether block can be held /// Whether block can be held
pub can_hold: bool, pub can_hold: bool,
/// What kinds of attacks the block applies to /// What kinds of attacks the block applies to

View File

@ -14,8 +14,8 @@ use crate::{
}, },
quadruped_low, quadruped_medium, quadruped_small, ship, quadruped_low, quadruped_medium, quadruped_small, ship,
skills::{Skill, SwimSkill, SKILL_MODIFIERS}, skills::{Skill, SwimSkill, SKILL_MODIFIERS},
theropod, Body, CharacterAbility, CharacterState, Density, InputAttr, InputKind, theropod, Body, CharacterState, Density, InputAttr, InputKind, InventoryAction, Melee,
InventoryAction, Melee, StateUpdate, StateUpdate,
}, },
consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE}, consts::{FRIC_GROUND, GRAVITY, MAX_PICKUP_RANGE},
event::{LocalEvent, ServerEvent}, event::{LocalEvent, ServerEvent},
@ -1247,15 +1247,16 @@ pub fn handle_input(
input: InputKind, input: InputKind,
) { ) {
match input { match input {
InputKind::Primary | InputKind::Secondary | InputKind::Ability(_) | InputKind::Roll => { InputKind::Primary
| InputKind::Secondary
| InputKind::Ability(_)
| InputKind::Block
| InputKind::Roll => {
handle_ability(data, update, output_events, input); handle_ability(data, update, output_events, input);
}, },
InputKind::Jump => { InputKind::Jump => {
handle_jump(data, output_events, update, 1.0); handle_jump(data, output_events, update, 1.0);
}, },
InputKind::Block => {
handle_block_input(data, update);
},
InputKind::Fly => {}, InputKind::Fly => {},
} }
} }
@ -1271,30 +1272,6 @@ pub fn attempt_input(
} }
} }
/// Checks that player can block, then attempts to block
pub fn handle_block_input(data: &JoinData<'_>, update: &mut StateUpdate) -> bool {
let can_block = |equip_slot| matches!(unwrap_tool_data(data, equip_slot), Some((kind, _)) if kind.can_block());
let hands = get_hands(data);
if input_is_pressed(data, InputKind::Block)
&& (can_block(EquipSlot::ActiveMainhand)
|| (hands.0.is_none() && can_block(EquipSlot::ActiveOffhand)))
{
let ability = CharacterAbility::default_block();
if ability.requirements_paid(data, update) {
update.character = CharacterState::from((
&ability,
AbilityInfo::from_input(data, false, InputKind::Block, Default::default()),
data,
));
true
} else {
false
}
} else {
false
}
}
/// Returns whether an interrupt occurred /// Returns whether an interrupt occurred
pub fn handle_interrupts( pub fn handle_interrupts(
data: &JoinData, data: &JoinData,
@ -1314,8 +1291,8 @@ pub fn handle_interrupts(
}); });
if can_dodge && input_is_pressed(data, InputKind::Roll) { if can_dodge && input_is_pressed(data, InputKind::Roll) {
handle_ability(data, update, output_events, InputKind::Roll) handle_ability(data, update, output_events, InputKind::Roll)
} else if can_block { } else if can_block && input_is_pressed(data, InputKind::Block) {
handle_block_input(data, update) handle_ability(data, update, output_events, InputKind::Block)
} else { } else {
false false
} }

View File

@ -14,7 +14,7 @@ use crate::{
use authc::Uuid; use authc::Uuid;
use common::{ use common::{
combat, combat,
combat::DamageContributor, combat::{AttackSource, DamageContributor},
comp::{ comp::{
self, aura, buff, self, aura, buff,
chat::{KillSource, KillType}, chat::{KillSource, KillType},
@ -1361,7 +1361,12 @@ pub fn handle_combo_change(server: &Server, entity: EcsEntity, change: i32) {
} }
} }
pub fn handle_parry_hook(server: &Server, defender: EcsEntity, attacker: Option<EcsEntity>) { pub fn handle_parry_hook(
server: &Server,
defender: EcsEntity,
attacker: Option<EcsEntity>,
source: AttackSource,
) {
let ecs = &server.state.ecs(); let ecs = &server.state.ecs();
let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>(); let server_eventbus = ecs.read_resource::<EventBus<ServerEvent>>();
// Reset character state of defender // Reset character state of defender
@ -1379,7 +1384,7 @@ pub fn handle_parry_hook(server: &Server, defender: EcsEntity, attacker: Option<
// Refund half the energy of entering the block for a successful parry // Refund half the energy of entering the block for a successful parry
server_eventbus.emit_now(ServerEvent::EnergyChange { server_eventbus.emit_now(ServerEvent::EnergyChange {
entity: defender, entity: defender,
change: c.static_data.energy_cost / 2.0, change: c.static_data.energy_regen,
}); });
true true
}, },
@ -1391,7 +1396,7 @@ pub fn handle_parry_hook(server: &Server, defender: EcsEntity, attacker: Option<
} }
}; };
if let Some(attacker) = attacker { if let Some(attacker) = attacker && matches!(source, AttackSource::Melee){
// When attacker is parried, add the parried debuff for 2 seconds, which slows // When attacker is parried, add the parried debuff for 2 seconds, which slows
// them // them
let data = buff::BuffData::new(1.0, Some(Secs(2.0)), None); let data = buff::BuffData::new(1.0, Some(Secs(2.0)), None);

View File

@ -237,9 +237,11 @@ impl Server {
ServerEvent::ComboChange { entity, change } => { ServerEvent::ComboChange { entity, change } => {
handle_combo_change(self, entity, change) handle_combo_change(self, entity, change)
}, },
ServerEvent::ParryHook { defender, attacker } => { ServerEvent::ParryHook {
handle_parry_hook(self, defender, attacker) defender,
}, attacker,
source,
} => handle_parry_hook(self, defender, attacker, source),
ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(self, entity, id), ServerEvent::RequestSiteInfo { entity, id } => handle_site_info(self, entity, id),
ServerEvent::MineBlock { entity, pos, tool } => { ServerEvent::MineBlock { entity, pos, tool } => {
handle_mine_block(self, entity, pos, tool) handle_mine_block(self, entity, pos, tool)

View File

@ -51,7 +51,11 @@ impl Animation for BlockAnimation {
next.second.orientation = Quaternion::rotation_z(0.0); next.second.orientation = Quaternion::rotation_z(0.0);
match ability_id { match ability_id {
None => { None
| Some("common.abilities.sword.basic_guard")
| Some("common.abilities.axe.basic_guard")
| Some("common.abilities.hammer.basic_guard")
| Some("common.abilities.sword.defensive_guard") => {
let speed = Vec2::<f32>::from(velocity).magnitude(); let speed = Vec2::<f32>::from(velocity).magnitude();
let (movement1base, move2, movement3) = match stage_section { let (movement1base, move2, movement3) = match stage_section {