Merge branch 'sam/diminishing-cc' into 'master'

Diminishing returns for certain crowd control debuffs

See merge request veloren/veloren!4482
This commit is contained in:
Samuel Keiffer 2024-05-29 15:50:09 +00:00
commit 79d97cdc22
12 changed files with 83 additions and 10 deletions

BIN
assets/voxygen/element/de_buffs/buff_resilience.png (Stored with Git LFS) Normal file

Binary file not shown.

View File

@ -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

View File

@ -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() {

View File

@ -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<BuffEffect> {
// Normalized nonlinear scaling
@ -534,6 +541,7 @@ impl BuffKind {
BuffEffect::MovementSpeed(0.7),
BuffEffect::DamagedEffect(DamagedEffect::Energy(data.strength * 10.0)),
],
BuffKind::Resilience => vec![BuffEffect::CrowdControlResistance(data.strength)],
}
}
@ -553,21 +561,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<f32> {
match self {
BuffKind::Concussion => Some(0.3),
BuffKind::Frozen => Some(data.strength),
_ => None,
}
}
}
// Struct used to store data relevant to a buff
@ -711,6 +735,8 @@ pub enum BuffEffect {
DeathEffect(DeathEffect),
/// Prevents use of auxiliary abilities
DisableAuxiliaryAbilities,
/// Reduces duration of crowd control debuffs
CrowdControlResistance(f32),
}
/// Actual de/buff.
@ -770,7 +796,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));

View File

@ -80,6 +80,7 @@ pub struct Stats {
/// This creates effects when the entity is killed
pub effects_on_death: Vec<DeathEffect>,
pub disable_auxiliary_abilities: bool,
pub crowd_control_resistance: f32,
}
impl Stats {
@ -109,6 +110,7 @@ impl Stats {
effects_on_damaged: Vec::new(),
effects_on_death: Vec::new(),
disable_auxiliary_abilities: false,
crowd_control_resistance: 0.0,
}
}

View File

@ -827,5 +827,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;
},
};
}

View File

@ -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 {

View File

@ -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<Item = Self>,
(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);
}
},

View File

@ -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"
},

View File

@ -818,6 +818,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",

View File

@ -5260,6 +5260,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,

View File

@ -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<String> {
| 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<String> {
| BuffKind::Winded
| BuffKind::Concussion
| BuffKind::Staggered
| BuffKind::Tenacity => Cow::Borrowed(""),
| BuffKind::Tenacity
| BuffKind::Resilience => Cow::Borrowed(""),
}
} else if let BuffKind::Saturation
| BuffKind::Regeneration