From 07220dfee8d5f7d7e55a356d9bf0ba3847138531 Mon Sep 17 00:00:00 2001 From: Sam Date: Mon, 27 May 2024 20:25:12 -0400 Subject: [PATCH] Diminishing returns for certain crowd control debuffs --- .../element/de_buffs/buff_resilience.png | 3 ++ assets/voxygen/i18n/en/buff.ftl | 3 ++ common/src/cmd.rs | 1 + common/src/comp/buff.rs | 36 ++++++++++++++++--- common/src/comp/stats.rs | 2 ++ common/systems/src/buff.rs | 3 ++ server/src/cmd.rs | 3 +- server/src/events/entity_manipulation.rs | 30 +++++++++++++++- voxygen/i18n-helpers/src/lib.rs | 3 +- voxygen/src/hud/img_ids.rs | 1 + voxygen/src/hud/mod.rs | 1 + voxygen/src/hud/util.rs | 7 ++-- 12 files changed, 83 insertions(+), 10 deletions(-) create mode 100644 assets/voxygen/element/de_buffs/buff_resilience.png diff --git a/assets/voxygen/element/de_buffs/buff_resilience.png b/assets/voxygen/element/de_buffs/buff_resilience.png new file mode 100644 index 0000000000..4fbd16e31d --- /dev/null +++ b/assets/voxygen/element/de_buffs/buff_resilience.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c35213bdeff77ccbab82f2215c3176f1b565264b87cf90d4b49772dcfca1d1f3 +size 1027 diff --git a/assets/voxygen/i18n/en/buff.ftl b/assets/voxygen/i18n/en/buff.ftl index 183d843647..02e8c88309 100644 --- a/assets/voxygen/i18n/en/buff.ftl +++ b/assets/voxygen/i18n/en/buff.ftl @@ -139,6 +139,9 @@ buff-staggered = Staggered ## Tenacity buff-tenacity = Tenacity .desc = You are not only able to shrug off heavier attacks, they energize you as well. However you are also slower. +## Resilience +buff-resilience = Resilience + .desc = After having just taken a debilitating attack, you become more resilient to future incapaciting effects. ## 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 aea0a895a3..80157817ef 100644 --- a/common/src/cmd.rs +++ b/common/src/cmd.rs @@ -195,6 +195,7 @@ lazy_static! { BuffKind::Concussion => "concussion", BuffKind::Staggered => "staggered", BuffKind::Tenacity => "tenacity", + BuffKind::Resilience => "resilience", }; let mut buff_parser = HashMap::new(); for kind in BuffKind::iter() { diff --git a/common/src/comp/buff.rs b/common/src/comp/buff.rs index 51a4c1d877..10385b85a2 100644 --- a/common/src/comp/buff.rs +++ b/common/src/comp/buff.rs @@ -141,6 +141,12 @@ pub enum BuffKind { /// with strength, 1.0 is 10 energy per hit. Movement speed is decreased to /// 70%. Tenacity, + /// Applies to some debuffs that have strong CC effects. Automatically + /// gained upon receiving those debuffs, and causes future instances of + /// those debuffs to be applied with reduced duration. + /// Strength linearly decreases the duration of newly applied, affected + /// debuffs, 0.5 is a 50% reduction. + Resilience, // ================= // DEBUFFS // ================= @@ -261,7 +267,8 @@ impl BuffKind { | BuffKind::Bloodfeast | BuffKind::Berserk | BuffKind::ScornfulTaunt - | BuffKind::Tenacity => BuffDescriptor::SimplePositive, + | BuffKind::Tenacity + | BuffKind::Resilience => BuffDescriptor::SimplePositive, BuffKind::Bleeding | BuffKind::Cursed | BuffKind::Burning @@ -310,7 +317,7 @@ impl BuffKind { /// Checks if multiple instances of the buff should be processed, instead of /// only the strongest. - pub fn stacks(self) -> bool { matches!(self, BuffKind::PotionSickness) } + pub fn stacks(self) -> bool { matches!(self, BuffKind::PotionSickness | BuffKind::Resilience) } pub fn effects(&self, data: &BuffData, stats: Option<&Stats>) -> Vec { // Normalized nonlinear scaling @@ -531,6 +538,7 @@ impl BuffKind { BuffEffect::MovementSpeed(0.7), BuffEffect::DamagedEffect(DamagedEffect::Energy(data.strength * 10.0)), ], + BuffKind::Resilience => vec![BuffEffect::CrowdControlResistance(data.strength)], } } @@ -550,21 +558,37 @@ impl BuffKind { &self, mut data: BuffData, source_mass: Option<&Mass>, - dest_mass: Option<&Mass>, + dest_info: DestInfo, ) -> BuffData { // TODO: Remove clippy allow after another buff needs this #[allow(clippy::single_match)] match self { BuffKind::Rooted => { let source_mass = source_mass.map_or(50.0, |m| m.0 as f64); - let dest_mass = dest_mass.map_or(50.0, |m| m.0 as f64); + let dest_mass = dest_info.mass.map_or(50.0, |m| m.0 as f64); let ratio = (source_mass / dest_mass).min(1.0); data.duration = data.duration.map(|dur| Secs(dur.0 * ratio)); }, _ => {}, } + if self.resilience_ccr_strength(data).is_some() { + let dur_mult = dest_info + .stats + .map_or(1.0, |s| (1.0 - s.crowd_control_resistance).max(0.0)); + data.duration = data.duration.map(|dur| dur * dur_mult as f64); + } data } + + /// If a buff kind should also give resilience when applied, return the + /// strength that resilience should have, otherwise return None + pub fn resilience_ccr_strength(&self, data: BuffData) -> Option { + match self { + BuffKind::Concussion => Some(0.3), + BuffKind::Frozen => Some(data.strength), + _ => None, + } + } } // Struct used to store data relevant to a buff @@ -704,6 +728,8 @@ pub enum BuffEffect { DeathEffect(DeathEffect), /// Prevents use of auxiliary abilities DisableAuxiliaryAbilities, + /// Reduces duration of crowd control debuffs + CrowdControlResistance(f32), } /// Actual de/buff. @@ -763,7 +789,7 @@ impl Buff { // Create source_info if we need more parameters from source source_mass: Option<&Mass>, ) -> Self { - let data = kind.modify_data(data, source_mass, dest_info.mass); + let data = kind.modify_data(data, source_mass, dest_info); let effects = kind.effects(&data, dest_info.stats); let cat_ids = kind.extend_cat_ids(cat_ids); let start_time = Time(time.0 + data.delay.map_or(0.0, |delay| delay.0)); diff --git a/common/src/comp/stats.rs b/common/src/comp/stats.rs index 0ba3c3ad36..a0be506813 100644 --- a/common/src/comp/stats.rs +++ b/common/src/comp/stats.rs @@ -78,6 +78,7 @@ pub struct Stats { /// This creates effects when the entity is killed pub effects_on_death: Vec, pub disable_auxiliary_abilities: bool, + pub crowd_control_resistance: f32, } impl Stats { @@ -105,6 +106,7 @@ impl Stats { effects_on_damaged: Vec::new(), effects_on_death: Vec::new(), disable_auxiliary_abilities: false, + crowd_control_resistance: 0.0, } } diff --git a/common/systems/src/buff.rs b/common/systems/src/buff.rs index 6347b102fc..edf8b62d47 100644 --- a/common/systems/src/buff.rs +++ b/common/systems/src/buff.rs @@ -817,5 +817,8 @@ fn execute_effect( BuffEffect::DamagedEffect(effect) => stat.effects_on_damaged.push(effect.clone()), BuffEffect::DeathEffect(effect) => stat.effects_on_death.push(effect.clone()), BuffEffect::DisableAuxiliaryAbilities => stat.disable_auxiliary_abilities = true, + BuffEffect::CrowdControlResistance(ccr) => { + stat.crowd_control_resistance += ccr; + }, }; } diff --git a/server/src/cmd.rs b/server/src/cmd.rs index a5327004b3..00b5fc16cc 100644 --- a/server/src/cmd.rs +++ b/server/src/cmd.rs @@ -4556,7 +4556,8 @@ fn build_buff( | BuffKind::Winded | BuffKind::Concussion | BuffKind::Staggered - | BuffKind::Tenacity => { + | BuffKind::Tenacity + | BuffKind::Resilience => { if buff_kind.is_simple() { unreachable!("is_simple() above") } else { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index ad66f2fa23..c5336aa45b 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -1617,11 +1617,13 @@ impl ServerEvent for BuffEvent { WriteStorage<'a, comp::Buffs>, ReadStorage<'a, Body>, ReadStorage<'a, Health>, + ReadStorage<'a, Stats>, + ReadStorage<'a, comp::Mass>, ); fn handle( events: impl ExactSizeIterator, - (time, mut buffs, bodies, healths): Self::SystemData<'_>, + (time, mut buffs, bodies, healths, stats, masses): Self::SystemData<'_>, ) { for ev in events { if let Some(mut buffs) = buffs.get_mut(ev.entity) { @@ -1633,6 +1635,32 @@ impl ServerEvent for BuffEvent { .map_or(false, |body| body.immune_to(new_buff.kind)) && healths.get(ev.entity).map_or(true, |h| !h.is_dead) { + if let Some(strength) = + new_buff.kind.resilience_ccr_strength(new_buff.data) + { + let resilience_buff = buff::Buff::new( + BuffKind::Resilience, + buff::BuffData::new( + strength, + Some( + new_buff + .data + .duration + .map_or(Secs(30.0), |dur| dur * 5.0), + ), + ), + Vec::new(), + BuffSource::Buff, + *time, + buff::DestInfo { + stats: stats.get(ev.entity), + mass: masses.get(ev.entity), + }, + // There is no source entity + None, + ); + buffs.insert(resilience_buff, *time); + } buffs.insert(new_buff, *time); } }, diff --git a/voxygen/i18n-helpers/src/lib.rs b/voxygen/i18n-helpers/src/lib.rs index 92ee927ba6..87bc472cb7 100644 --- a/voxygen/i18n-helpers/src/lib.rs +++ b/voxygen/i18n-helpers/src/lib.rs @@ -397,7 +397,8 @@ fn get_buff_ident(buff: BuffKind) -> &'static str { | BuffKind::Bloodfeast | BuffKind::Berserk | BuffKind::ScornfulTaunt - | BuffKind::Tenacity => { + | BuffKind::Tenacity + | BuffKind::Resilience => { tracing::error!("Player was killed by a positive buff!"); "mysterious" }, diff --git a/voxygen/src/hud/img_ids.rs b/voxygen/src/hud/img_ids.rs index a3ecfb0f5e..4ac9908a01 100644 --- a/voxygen/src/hud/img_ids.rs +++ b/voxygen/src/hud/img_ids.rs @@ -815,6 +815,7 @@ image_ids! { buff_frigid: "voxygen.element.de_buffs.buff_frigid", buff_scornfultaunt: "voxygen.element.de_buffs.buff_scornfultaunt", buff_tenacity: "voxygen.element.de_buffs.buff_tenacity", + buff_resilience: "voxygen.element.de_buffs.buff_resilience", // Debuffs debuff_skull_0: "voxygen.element.de_buffs.debuff_skull_0", diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 8ed9bd5fb4..c1b759fcc8 100755 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -5256,6 +5256,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id { BuffKind::Flame => imgs.buff_flame, BuffKind::Frigid => imgs.buff_frigid, BuffKind::Lifesteal => imgs.buff_lifesteal, + BuffKind::Resilience => imgs.buff_resilience, // TODO: Get image // BuffKind::SalamanderAspect => imgs.debuff_burning_0, BuffKind::ImminentCritical => imgs.buff_imminentcritical, diff --git a/voxygen/src/hud/util.rs b/voxygen/src/hud/util.rs index 200480b788..09c3900b1d 100644 --- a/voxygen/src/hud/util.rs +++ b/voxygen/src/hud/util.rs @@ -199,6 +199,7 @@ fn buff_key(buff: BuffKind) -> &'static str { BuffKind::Berserk => "buff-berserk", BuffKind::ScornfulTaunt => "buff-scornfultaunt", BuffKind::Tenacity => "buff-tenacity", + BuffKind::Resilience => "buff-resilience", // Debuffs BuffKind::Bleeding => "buff-bleed", BuffKind::Cursed => "buff-cursed", @@ -330,7 +331,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec { | BuffKind::Winded | BuffKind::Concussion | BuffKind::Staggered - | BuffKind::Tenacity => Cow::Borrowed(""), + | BuffKind::Tenacity + | BuffKind::Resilience => Cow::Borrowed(""), }; write!(&mut description, "{}", buff_desc).unwrap(); @@ -386,7 +388,8 @@ pub fn consumable_desc(effects: &Effects, i18n: &Localization) -> Vec { | BuffKind::Winded | BuffKind::Concussion | BuffKind::Staggered - | BuffKind::Tenacity => Cow::Borrowed(""), + | BuffKind::Tenacity + | BuffKind::Resilience => Cow::Borrowed(""), } } else if let BuffKind::Saturation | BuffKind::Regeneration