From a8212d6f416ced95438d3b5b1574371c01048d4a Mon Sep 17 00:00:00 2001 From: Sam Date: Tue, 6 Sep 2022 21:23:12 -0400 Subject: [PATCH] Dive melee now scales its attack off of the entity's vertical speed. Parries now cause the attacker to effectively have a recover that is either twice as long or 0.5s longer, whichever is more. Counters now deal twice as much damage to the target if the target is in the buildup portion of an ability. --- .../common/abilities/sword/cleaving_dive.ron | 8 +- .../abilities/sword/parrying_counter.ron | 1 + assets/voxygen/i18n/en/buff.ftl | 3 + common/src/cmd.rs | 1 + common/src/combat.rs | 18 ++ common/src/comp/ability.rs | 8 +- common/src/comp/buff.rs | 8 +- common/src/comp/character_state.rs | 195 +++++++++++++++++- common/src/states/combo_melee.rs | 22 +- common/src/states/combo_melee2.rs | 15 +- common/src/states/dive_melee.rs | 10 + server/src/events/entity_manipulation.rs | 36 +++- voxygen/i18n-helpers/src/lib.rs | 2 +- voxygen/src/hud/mod.rs | 4 + voxygen/src/hud/util.rs | 6 +- 15 files changed, 313 insertions(+), 24 deletions(-) diff --git a/assets/common/abilities/sword/cleaving_dive.ron b/assets/common/abilities/sword/cleaving_dive.ron index 5659fb767c..6239263c9d 100644 --- a/assets/common/abilities/sword/cleaving_dive.ron +++ b/assets/common/abilities/sword/cleaving_dive.ron @@ -6,11 +6,17 @@ DiveMelee( recover_duration: 0.3, melee_constructor: ( kind: Slash( - damage: 30, + damage: 20, poise: 0, knockback: 0, energy_regen: 10, ), + scaled: Some(Slash( + damage: 10, + poise: 0, + knockback: 0, + energy_regen: 10, + )), range: 6.0, angle: 15.0, multi_target: true, diff --git a/assets/common/abilities/sword/parrying_counter.ron b/assets/common/abilities/sword/parrying_counter.ron index 507cb8e4b1..17f8660403 100644 --- a/assets/common/abilities/sword/parrying_counter.ron +++ b/assets/common/abilities/sword/parrying_counter.ron @@ -10,6 +10,7 @@ ComboMelee2( ), range: 6.0, angle: 5.0, + damage_effect: Some(BuildupsVulnerable), ), buildup_duration: 0.05, swing_duration: 0.1, diff --git a/assets/voxygen/i18n/en/buff.ftl b/assets/voxygen/i18n/en/buff.ftl index c4d1d9e1eb..16d54192ff 100644 --- a/assets/voxygen/i18n/en/buff.ftl +++ b/assets/voxygen/i18n/en/buff.ftl @@ -64,6 +64,9 @@ buff-desc-ensnared = Vines grasp at your legs, impeding your movement. ## Fortitude buff-title-fortitude = Fortitude buff-desc-fortitude = You can withstand staggers. +## Parried +buff-title-parried = Parried +buff-desc-parried = You were parried and now are slow to recover. ## Util buff-text-over_seconds = over { $dur_secs } seconds buff-text-for_seconds = for { $dur_secs } seconds diff --git a/common/src/cmd.rs b/common/src/cmd.rs index 9af913f81f..5d5db82595 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -154,6 +154,7 @@ lazy_static! { BuffKind::Poisoned => "poisoned", BuffKind::Hastened => "hastened", BuffKind::Fortitude => "fortitude", + BuffKind::Parried => "parried", }; let mut buff_parser = HashMap::new(); for kind in BuffKind::iter() { diff --git a/common/src/combat.rs b/common/src/combat.rs index f15c8ee1d7..53e5c20e0d 100644 --- a/common/src/combat.rs +++ b/common/src/combat.rs @@ -12,6 +12,7 @@ use crate::{ }, event::ServerEvent, outcome::Outcome, + states::utils::StageSection, uid::{Uid, UidAllocator}, util::Dir, }; @@ -441,6 +442,16 @@ impl Attack { } } }, + CombatEffect::BuildupsVulnerable => { + if target.char_state.map_or(false, |cs| { + matches!(cs.stage_section(), Some(StageSection::Buildup)) + }) { + emit(ServerEvent::HealthChange { + entity: target.entity, + change, + }); + } + }, } } } @@ -605,6 +616,8 @@ impl Attack { } } }, + // Only has an effect when attached to a damage + CombatEffect::BuildupsVulnerable => {}, } } } @@ -738,6 +751,11 @@ pub enum CombatEffect { Combo(i32), // If the attack kills the target, reset the melee attack ResetMelee, + // If the attack hits the target while they are in the buildup portion of a character state, + // deal double damage Only has an effect when attached to a damage, otherwise does nothing + // if only attached to the attack TODO: Maybe try to make it do something if tied to + // attack, not sure if it should double count in that instance? + BuildupsVulnerable, } #[cfg(not(target_arch = "wasm32"))] diff --git a/common/src/comp/ability.rs b/common/src/comp/ability.rs index aacca400af..bcab5e6652 100644 --- a/common/src/comp/ability.rs +++ b/common/src/comp/ability.rs @@ -2507,19 +2507,21 @@ impl From<(&CharacterAbility, AbilityInfo, &JoinData<'_>)> for CharacterState { recover_duration, melee_constructor, energy_cost: _, - vertical_speed: _, + vertical_speed, meta: _, } => CharacterState::DiveMelee(dive_melee::Data { static_data: dive_melee::StaticData { movement_duration: Duration::from_secs_f32(*movement_duration), swing_duration: Duration::from_secs_f32(*swing_duration), recover_duration: Duration::from_secs_f32(*recover_duration), + vertical_speed: *vertical_speed, melee_constructor: *melee_constructor, ability_info, }, timer: Duration::default(), stage_section: StageSection::Movement, exhausted: false, + max_vertical_speed: 0.0, }), CharacterAbility::RiposteMelee { energy_cost: _, @@ -2598,9 +2600,13 @@ pub enum SwordStance { bitflags::bitflags! { #[derive(Default, Serialize, Deserialize)] pub struct Capability: u8 { + // Allows rolls to interrupt the ability at any point, not just during buildup const ROLL_INTERRUPT = 0b00000001; + // Allows blocking to interrupt the ability at any point const BLOCK_INTERRUPT = 0b00000010; + // When the ability is in the buyildup section, it counts as a parry const BUILDUP_PARRIES = 0b00000100; + // When in the ability, an entity only receives half as much poise damage const POISE_RESISTANT = 0b00001000; } } diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 26088496df..2672cb65d4 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -87,6 +87,10 @@ pub enum BuffKind { /// Drain stamina to a creature over time /// Strength should be the energy per second of the debuff Poisoned, + /// Results from having an attack parried. + /// Causes your attack speed to be slower to emulate the recover duration of + /// an ability being lengthened. + Parried, } #[cfg(not(target_arch = "wasm32"))] @@ -113,7 +117,8 @@ impl BuffKind { | BuffKind::Frozen | BuffKind::Wet | BuffKind::Ensnared - | BuffKind::Poisoned => false, + | BuffKind::Poisoned + | BuffKind::Parried => false, } } @@ -390,6 +395,7 @@ impl Buff { vec![BuffEffect::PoiseReduction(data.strength)], data.duration, ), + BuffKind::Parried => (vec![BuffEffect::AttackSpeed(0.5)], data.duration), }; Buff { kind, diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index ddbf779352..0c3cf4178e 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -13,7 +13,7 @@ use crate::{ }; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage}; -use std::collections::BTreeMap; +use std::{collections::BTreeMap, time::Duration}; use strum::Display; /// Data returned from character behavior fn's to Character Behavior System. @@ -571,6 +571,199 @@ impl CharacterState { CharacterState::RapidMelee(data) => Some(data.stage_section), } } + + pub fn durations(&self) -> Option { + match &self { + CharacterState::Idle(_) => None, + CharacterState::Talk => None, + CharacterState::Climb(_) => None, + CharacterState::Wallrun(_) => None, + CharacterState::Skate(_) => None, + CharacterState::Glide(_) => None, + CharacterState::GlideWield(_) => None, + CharacterState::Stunned(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::Sit => None, + CharacterState::Dance => None, + CharacterState::BasicBlock(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::Roll(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + recover: Some(data.static_data.recover_duration), + movement: Some(data.static_data.movement_duration), + ..Default::default() + }), + CharacterState::Wielding(_) => None, + CharacterState::Equipping(_) => None, + CharacterState::ComboMelee(data) => { + let stage_index = data.stage_index(); + let stage = data.static_data.stage_data[stage_index]; + Some(DurationsInfo { + buildup: Some(stage.base_buildup_duration), + action: Some(stage.base_swing_duration), + recover: Some(stage.base_recover_duration), + ..Default::default() + }) + }, + CharacterState::ComboMelee2(data) => { + let strike = data.strike_data(); + Some(DurationsInfo { + buildup: Some(strike.buildup_duration), + action: Some(strike.swing_duration), + recover: Some(strike.recover_duration), + ..Default::default() + }) + }, + CharacterState::BasicMelee(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::BasicRanged(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::Boost(data) => Some(DurationsInfo { + movement: Some(data.static_data.movement_duration), + ..Default::default() + }), + CharacterState::DashMelee(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + charge: Some(data.static_data.charge_duration), + ..Default::default() + }), + CharacterState::LeapMelee(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + movement: Some(data.static_data.movement_duration), + ..Default::default() + }), + CharacterState::SpinMelee(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::ChargedMelee(data) => Some(DurationsInfo { + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + charge: Some(data.static_data.charge_duration), + ..Default::default() + }), + CharacterState::ChargedRanged(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + recover: Some(data.static_data.recover_duration), + charge: Some(data.static_data.charge_duration), + ..Default::default() + }), + CharacterState::RepeaterRanged(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.shoot_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::Shockwave(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::BasicBeam(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::BasicAura(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.cast_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::Blink(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::BasicSummon(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.cast_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::SelfBuff(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.cast_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::SpriteSummon(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.cast_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::UseItem(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.use_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::SpriteInteract(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.use_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::FinisherMelee(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::Music(data) => Some(DurationsInfo { + action: Some(data.static_data.play_duration), + ..Default::default() + }), + CharacterState::DiveMelee(data) => Some(DurationsInfo { + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + movement: Some(data.static_data.movement_duration), + ..Default::default() + }), + CharacterState::RiposteMelee(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + CharacterState::RapidMelee(data) => Some(DurationsInfo { + buildup: Some(data.static_data.buildup_duration), + action: Some(data.static_data.swing_duration), + recover: Some(data.static_data.recover_duration), + ..Default::default() + }), + } + } +} + +#[derive(Default, Copy, Clone)] +pub struct DurationsInfo { + pub buildup: Option, + pub action: Option, + pub recover: Option, + pub movement: Option, + pub charge: Option, } impl Default for CharacterState { diff --git a/common/src/states/combo_melee.rs b/common/src/states/combo_melee.rs index 9ecce143a0..d588c1854f 100644 --- a/common/src/states/combo_melee.rs +++ b/common/src/states/combo_melee.rs @@ -162,14 +162,7 @@ impl CharacterBehavior for Data { handle_move(data, &mut update, 0.4); - // Index should be `self.stage - 1`, however in cases of client-server desync - // this can cause panics. This ensures that `self.stage - 1` is valid, and if it - // isn't, index of 0 is used, which is always safe. - let stage_index = self - .static_data - .stage_data - .get(self.stage as usize - 1) - .map_or(0, |_| self.stage as usize - 1); + let stage_index = self.stage_index(); let speed_modifier = 1.0 + self.static_data.max_speed_increase @@ -372,6 +365,19 @@ impl CharacterBehavior for Data { } } +impl Data { + /// Index should be `self.stage - 1`, however in cases of client-server desync + /// this can cause panics. This ensures that `self.stage - 1` is valid, and if it + /// isn't, index of 0 is used, which is always safe. + pub fn stage_index(&self) -> usize { + self + .static_data + .stage_data + .get(self.stage as usize - 1) + .map_or(0, |_| self.stage as usize - 1) + } +} + fn reset_state( data: &Data, join: &JoinData, diff --git a/common/src/states/combo_melee2.rs b/common/src/states/combo_melee2.rs index 1e83b477bd..bea8d28146 100644 --- a/common/src/states/combo_melee2.rs +++ b/common/src/states/combo_melee2.rs @@ -122,8 +122,7 @@ impl CharacterBehavior for Data { handle_move(data, &mut update, 0.7); handle_interrupts(data, &mut update, Some(ability_input)); - let strike_data = - self.static_data.strikes[self.completed_strikes % self.static_data.strikes.len()]; + let strike_data = self.strike_data(); match self.stage_section { Some(StageSection::Buildup) => { @@ -149,10 +148,8 @@ impl CharacterBehavior for Data { } if input_is_pressed(data, ability_input) { if let CharacterState::ComboMelee2(c) = &mut update.character { - // Only allow next strike if attack is a stance or has multiple strikes that - // have not finished yet - c.start_next_strike = c.static_data.is_stance - || (c.completed_strikes + 1) < c.static_data.strikes.len(); + // Only have the next strike skip the recover period of this strike if not every strike in the combo is complete yet + c.start_next_strike = (c.completed_strikes + 1) < c.static_data.strikes.len(); } } if self.timer.as_secs_f32() @@ -251,6 +248,12 @@ impl CharacterBehavior for Data { } } +impl Data { + pub fn strike_data(&self) -> &Strike { + &self.static_data.strikes[self.completed_strikes % self.static_data.strikes.len()] + } +} + fn next_strike(update: &mut StateUpdate) { if let CharacterState::ComboMelee2(c) = &mut update.character { if update diff --git a/common/src/states/dive_melee.rs b/common/src/states/dive_melee.rs index 1e37f5dae1..dab7621f14 100644 --- a/common/src/states/dive_melee.rs +++ b/common/src/states/dive_melee.rs @@ -18,6 +18,8 @@ pub struct StaticData { pub swing_duration: Duration, /// How long the state has until exiting pub recover_duration: Duration, + /// The minimum vertical speed the state needed + pub vertical_speed: f32, /// Used to construct the Melee attack pub melee_constructor: MeleeConstructor, /// What key is used to press ability @@ -35,12 +37,18 @@ pub struct Data { pub stage_section: StageSection, /// Whether the attack can deal more damage pub exhausted: bool, + /// The maximum negative vertical velocity achieved during the state + pub max_vertical_speed: f32, } impl CharacterBehavior for Data { fn behavior(&self, data: &JoinData, _output_events: &mut OutputEvents) -> StateUpdate { let mut update = StateUpdate::from(data); + if let CharacterState::DiveMelee(c) = &mut update.character { + c.max_vertical_speed = c.max_vertical_speed.max(-data.vel.0.z); + } + match self.stage_section { StageSection::Movement => { if data.physics.on_ground.is_some() { @@ -67,11 +75,13 @@ impl CharacterBehavior for Data { // Attack let crit_data = get_crit_data(data, self.static_data.ability_info); let buff_strength = get_buff_strength(data, self.static_data.ability_info); + let scaling = self.max_vertical_speed / self.static_data.vertical_speed; data.updater.insert( data.entity, self.static_data .melee_constructor + .handle_scaling(scaling) .create_melee(crit_data, buff_strength), ); diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index a3740ac163..09ae83dc5d 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -42,7 +42,7 @@ use rand_distr::Distribution; use specs::{ join::Join, saveload::MarkerAllocator, Builder, Entity as EcsEntity, Entity, WorldExt, }; -use std::{collections::HashMap, iter, time::Duration}; +use std::{collections::HashMap, iter, ops::Mul, time::Duration}; use tracing::{debug, error}; use vek::{Vec2, Vec3}; @@ -1224,8 +1224,9 @@ pub fn handle_combo_change(server: &Server, entity: EcsEntity, change: i32) { } } -pub fn handle_parry_hook(server: &Server, defender: EcsEntity, _attacker: Option) { +pub fn handle_parry_hook(server: &Server, defender: EcsEntity, attacker: Option) { let ecs = &server.state.ecs(); + let server_eventbus = ecs.read_resource::>(); // Reset character state of defender if let Some(mut char_state) = ecs .write_storage::() @@ -1247,7 +1248,36 @@ pub fn handle_parry_hook(server: &Server, defender: EcsEntity, _attacker: Option const PARRY_REWARD: f32 = 5.0; energy.change_by(PARRY_REWARD); } - // TODO: Some penalties for attacker + + if let Some(attacker) = attacker { + if let Some(char_state) = ecs.read_storage::().get(attacker) { + // By having a duration twice as long as either the recovery duration or 0.5 s, + // causes recover duration to effectively be either doubled or increased by 0.5 + // s when a buff is applied that halves their attack speed. + let duration = char_state + .durations() + .and_then(|durs| durs.recover) + .map_or(0.5, |dur| dur.as_secs_f32()) + .max(0.5) + .mul(2.0); + let data = buff::BuffData::new(1.0, Some(Duration::from_secs_f32(duration))); + let source = if let Some(uid) = ecs.read_storage::().get(defender) { + BuffSource::Character { by: *uid } + } else { + BuffSource::World + }; + let buff = buff::Buff::new( + BuffKind::Parried, + data, + vec![buff::BuffCategory::Physical], + source, + ); + server_eventbus.emit_now(ServerEvent::Buff { + entity: attacker, + buff_change: buff::BuffChange::Add(buff), + }); + } + } } pub fn handle_teleport_to(server: &Server, entity: EcsEntity, target: Uid, max_range: Option) { diff --git a/voxygen/i18n-helpers/src/lib.rs b/voxygen/i18n-helpers/src/lib.rs index 66717159b3..d2a6087064 100644 --- a/voxygen/i18n-helpers/src/lib.rs +++ b/voxygen/i18n-helpers/src/lib.rs @@ -106,7 +106,7 @@ pub fn localize_chat_message( tracing::error!("Player was killed by a positive buff!"); "hud-outcome-mysterious" }, - BuffKind::Wet | BuffKind::Ensnared | BuffKind::Poisoned => { + BuffKind::Wet | BuffKind::Ensnared | BuffKind::Poisoned | BuffKind::Parried => { tracing::error!("Player was killed by a debuff that doesn't do damage!"); "hud-outcome-mysterious" }, diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index eb2092717c..dba6a36ed7 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -4621,6 +4621,8 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id { BuffKind::Wet => imgs.debuff_wet_0, BuffKind::Ensnared => imgs.debuff_ensnared_0, BuffKind::Poisoned => imgs.debuff_poisoned_0, + // TODO: Get unique icon + BuffKind::Parried => imgs.debuff_crippled_0, } } @@ -4652,6 +4654,7 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> Cow localized_strings.get_msg("buff-title-wet"), BuffKind::Ensnared { .. } => localized_strings.get_msg("buff-title-ensnared"), BuffKind::Poisoned { .. } => localized_strings.get_msg("buff-title-poisoned"), + BuffKind::Parried { .. } => localized_strings.get_msg("buff-title-parried"), } } @@ -4687,6 +4690,7 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz BuffKind::Wet { .. } => localized_strings.get_msg("buff-desc-wet"), BuffKind::Ensnared { .. } => localized_strings.get_msg("buff-desc-ensnared"), BuffKind::Poisoned { .. } => localized_strings.get_msg("buff-desc-poisoned"), + BuffKind::Parried { .. } => localized_strings.get_msg("buff-desc-parried"), } } diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index 5f77fc81ef..8d32dbc9e0 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -184,7 +184,8 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> Vec { | BuffKind::Ensnared | BuffKind::Poisoned | BuffKind::Hastened - | BuffKind::Fortitude => Cow::Borrowed(""), + | BuffKind::Fortitude + | BuffKind::Parried => Cow::Borrowed(""), }; write!(&mut description, "{}", buff_desc).unwrap(); @@ -215,7 +216,8 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> Vec { | BuffKind::Ensnared | BuffKind::Poisoned | BuffKind::Hastened - | BuffKind::Fortitude => Cow::Borrowed(""), + | BuffKind::Fortitude + | BuffKind::Parried => Cow::Borrowed(""), } } else if let BuffKind::Saturation | BuffKind::Regeneration | BuffKind::EnergyRegen = buff.kind