mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge branch 'potion-sickness' into 'master'
Implement potion sickness, which causes diminishing returns on healing from potions. See merge request veloren/veloren!3756
This commit is contained in:
commit
25c8eb8444
@ -20,6 +20,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Fixed
|
||||
- Doors
|
||||
- Debug hitboxes now scale with the `Scale` component
|
||||
- Potion quaffing no longer makes characters practically immortal.
|
||||
|
||||
## [0.14.0] - 2023-01-07
|
||||
|
||||
|
1
Cargo.lock
generated
1
Cargo.lock
generated
@ -6720,6 +6720,7 @@ dependencies = [
|
||||
"fxhash",
|
||||
"hashbrown 0.12.3",
|
||||
"indexmap",
|
||||
"itertools",
|
||||
"kiddo 0.1.7",
|
||||
"lazy_static",
|
||||
"num-derive",
|
||||
|
@ -18,9 +18,10 @@
|
||||
)),
|
||||
items: [
|
||||
(10, "common.items.consumable.potion_big"),
|
||||
(10, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank3.fullskill"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -18,9 +18,10 @@
|
||||
)),
|
||||
items: [
|
||||
(10, "common.items.consumable.potion_big"),
|
||||
(10, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank3.fullskill"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -18,9 +18,10 @@
|
||||
)),
|
||||
items: [
|
||||
(10, "common.items.consumable.potion_big"),
|
||||
(10, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank3.fullskill"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -15,9 +15,10 @@
|
||||
)),
|
||||
items: [
|
||||
(25, "common.items.consumable.potion_big"),
|
||||
(25, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank3.fullskill"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -18,7 +18,8 @@
|
||||
)),
|
||||
items: [
|
||||
(10, "common.items.consumable.potion_big"),
|
||||
(10, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [],
|
||||
)
|
||||
)
|
||||
|
@ -21,7 +21,8 @@
|
||||
)),
|
||||
items: [
|
||||
(10, "common.items.consumable.potion_big"),
|
||||
(10, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [],
|
||||
)
|
||||
)
|
||||
|
@ -22,9 +22,10 @@
|
||||
)),
|
||||
items: [
|
||||
(5, "common.items.consumable.potion_minor"),
|
||||
(5, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank1.fullskill"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -22,9 +22,10 @@
|
||||
)),
|
||||
items: [
|
||||
(25, "common.items.consumable.potion_minor"),
|
||||
(25, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank2.fullskill"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -33,9 +33,10 @@
|
||||
)),
|
||||
items: [
|
||||
(50, "common.items.consumable.potion_med"),
|
||||
(50, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
SkillSetAsset("common.skillset.preset.rank3.fullskill"),
|
||||
],
|
||||
)
|
||||
)
|
||||
|
@ -33,6 +33,7 @@
|
||||
)),
|
||||
items: [
|
||||
(50, "common.items.consumable.potion_big"),
|
||||
(50, "common.items.food.sunflower_icetea"),
|
||||
],
|
||||
),
|
||||
meta: [
|
||||
|
@ -15,6 +15,15 @@ ItemDef(
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
Buff((
|
||||
kind: PotionSickness,
|
||||
data: (
|
||||
strength: 0.33,
|
||||
duration: Some(( secs: 45, nanos: 0, )),
|
||||
delay: Some(( secs: 1, nanos: 0, ))
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
]
|
||||
),
|
||||
quality: High,
|
||||
|
@ -15,6 +15,15 @@ ItemDef(
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
Buff((
|
||||
kind: PotionSickness,
|
||||
data: (
|
||||
strength: 0.33,
|
||||
duration: Some(( secs: 45, nanos: 0, )),
|
||||
delay: Some(( secs: 1, nanos: 0, ))
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
]
|
||||
),
|
||||
quality: Common,
|
||||
|
@ -15,6 +15,15 @@ ItemDef(
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
Buff((
|
||||
kind: PotionSickness,
|
||||
data: (
|
||||
strength: 0.33,
|
||||
duration: Some(( secs: 45, nanos: 0, )),
|
||||
delay: Some(( secs: 1, nanos: 0, ))
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
]
|
||||
),
|
||||
quality: Common,
|
||||
|
@ -15,6 +15,15 @@ ItemDef(
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
Buff((
|
||||
kind: PotionSickness,
|
||||
data: (
|
||||
strength: 0.33,
|
||||
duration: Some(( secs: 45, nanos: 0, )),
|
||||
delay: Some(( secs: 1, nanos: 0, ))
|
||||
),
|
||||
cat_ids: [Natural],
|
||||
)),
|
||||
]
|
||||
),
|
||||
quality: Common,
|
||||
|
BIN
assets/voxygen/element/de_buffs/debuff_potionsickness_0.png
(Stored with Git LFS)
Normal file
BIN
assets/voxygen/element/de_buffs/debuff_potionsickness_0.png
(Stored with Git LFS)
Normal file
Binary file not shown.
@ -67,6 +67,12 @@ buff-desc-fortitude = You can withstand staggers.
|
||||
## Parried
|
||||
buff-title-parried = Parried
|
||||
buff-desc-parried = You were parried and now are slow to recover.
|
||||
## Potion sickness
|
||||
buff-title-potionsickness = Potion sickness
|
||||
buff-desc-potionsickness = Potions heal you less after recently consuming a potion.
|
||||
buff-stat-potionsickness =
|
||||
Decreases the amount you heal from
|
||||
subsequent potions by { $strength }%.
|
||||
## Util
|
||||
buff-text-over_seconds = over { $dur_secs } seconds
|
||||
buff-text-for_seconds = for { $dur_secs } seconds
|
||||
|
@ -78,6 +78,7 @@ const int BLACK_SMOKE = 37;
|
||||
const int LIGHTNING = 38;
|
||||
const int STEAM = 39;
|
||||
const int BARRELORGAN = 40;
|
||||
const int POTION_SICKNESS = 41;
|
||||
|
||||
// meters per second squared (acceleration)
|
||||
const float earth_gravity = 9.807;
|
||||
@ -95,6 +96,12 @@ vec3 linear_motion(vec3 init_offs, vec3 vel) {
|
||||
return init_offs + vel * lifetime;
|
||||
}
|
||||
|
||||
vec3 quadratic_bezier_motion(vec3 start, vec3 ctrl0, vec3 end) {
|
||||
float t = lifetime;
|
||||
float u = 1 - lifetime;
|
||||
return u*u*start + t*u*ctrl0 + t*t*end;
|
||||
}
|
||||
|
||||
vec3 grav_vel(float grav) {
|
||||
return vec3(0, 0, -grav * lifetime);
|
||||
}
|
||||
@ -637,6 +644,18 @@ void main() {
|
||||
spin_in_axis(vec3(1,0,0),0)
|
||||
);
|
||||
break;
|
||||
case POTION_SICKNESS:
|
||||
attr = Attr(
|
||||
quadratic_bezier_motion(
|
||||
vec3(0.0),
|
||||
vec3(inst_dir.xy, 0.0),
|
||||
inst_dir
|
||||
),
|
||||
vec3((2.0 * (1 - slow_start(0.8)))),
|
||||
vec4(0.075, 0.625, 0, 1),
|
||||
spin_in_axis(vec3(1,0,0),0)
|
||||
);
|
||||
break;
|
||||
default:
|
||||
attr = Attr(
|
||||
linear_motion(
|
||||
|
@ -29,6 +29,7 @@ serde = { version = "1.0.110", features = ["derive", "rc"] }
|
||||
vek = { version = "0.15.8", features = ["serde"] }
|
||||
chrono = "0.4.22"
|
||||
chrono-tz = "0.6"
|
||||
itertools = "0.10"
|
||||
sha2 = "0.10"
|
||||
serde_json = "1.0.50"
|
||||
|
||||
|
@ -155,6 +155,7 @@ lazy_static! {
|
||||
BuffKind::Hastened => "hastened",
|
||||
BuffKind::Fortitude => "fortitude",
|
||||
BuffKind::Parried => "parried",
|
||||
BuffKind::PotionSickness => "potion_sickness",
|
||||
};
|
||||
let mut buff_parser = HashMap::new();
|
||||
for kind in BuffKind::iter() {
|
||||
|
@ -1039,6 +1039,7 @@ impl CombatBuff {
|
||||
BuffData::new(
|
||||
self.strength.to_strength(damage, strength_modifier),
|
||||
Some(Duration::from_secs_f32(self.dur_secs)),
|
||||
None,
|
||||
),
|
||||
Vec::new(),
|
||||
source,
|
||||
|
@ -144,6 +144,7 @@ impl AuraBuffConstructor {
|
||||
data: BuffData {
|
||||
strength: self.strength,
|
||||
duration: self.duration.map(Duration::from_secs_f32),
|
||||
delay: None,
|
||||
},
|
||||
category: self.category,
|
||||
source: BuffSource::Character { by: *uid },
|
||||
|
@ -3,6 +3,7 @@ use crate::uid::Uid;
|
||||
use core::{cmp::Ordering, time::Duration};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use hashbrown::HashMap;
|
||||
use itertools::Either;
|
||||
use serde::{Deserialize, Serialize};
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
use specs::{Component, DerefFlaggedStorage, VecStorage};
|
||||
@ -91,6 +92,9 @@ pub enum BuffKind {
|
||||
/// Causes your attack speed to be slower to emulate the recover duration of
|
||||
/// an ability being lengthened.
|
||||
Parried,
|
||||
/// Results from drinking a potion.
|
||||
/// Decreases the health gained from subsequent potions.
|
||||
PotionSickness,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -118,12 +122,21 @@ impl BuffKind {
|
||||
| BuffKind::Wet
|
||||
| BuffKind::Ensnared
|
||||
| BuffKind::Poisoned
|
||||
| BuffKind::Parried => false,
|
||||
| BuffKind::Parried
|
||||
| BuffKind::PotionSickness => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if buff should queue
|
||||
pub fn queues(self) -> bool { matches!(self, BuffKind::Saturation) }
|
||||
|
||||
/// Checks if the buff can affect other buff effects applied in the same
|
||||
/// tick.
|
||||
pub fn affects_subsequent_buffs(self) -> bool { matches!(self, BuffKind::PotionSickness) }
|
||||
|
||||
/// Checks if multiple instances of the buff should be processed, instead of
|
||||
/// only the strongest.
|
||||
pub fn stacks(self) -> bool { matches!(self, BuffKind::PotionSickness) }
|
||||
}
|
||||
|
||||
// Struct used to store data relevant to a buff
|
||||
@ -131,11 +144,18 @@ impl BuffKind {
|
||||
pub struct BuffData {
|
||||
pub strength: f32,
|
||||
pub duration: Option<Duration>,
|
||||
pub delay: Option<Duration>,
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
impl BuffData {
|
||||
pub fn new(strength: f32, duration: Option<Duration>) -> Self { Self { strength, duration } }
|
||||
pub fn new(strength: f32, duration: Option<Duration>, delay: Option<Duration>) -> Self {
|
||||
Self {
|
||||
strength,
|
||||
duration,
|
||||
delay,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// De/buff category ID.
|
||||
@ -194,6 +214,8 @@ pub enum BuffEffect {
|
||||
GroundFriction(f32),
|
||||
/// Reduces poise damage taken after armor is accounted for by this fraction
|
||||
PoiseReduction(f32),
|
||||
/// Reduces amount healed by consumables
|
||||
HealReduction { rate: f32 },
|
||||
}
|
||||
|
||||
/// Actual de/buff.
|
||||
@ -212,6 +234,7 @@ pub struct Buff {
|
||||
pub data: BuffData,
|
||||
pub cat_ids: Vec<BuffCategory>,
|
||||
pub time: Option<Duration>,
|
||||
pub delay: Option<Duration>,
|
||||
pub effects: Vec<BuffEffect>,
|
||||
pub source: BuffSource,
|
||||
}
|
||||
@ -396,16 +419,30 @@ impl Buff {
|
||||
data.duration,
|
||||
),
|
||||
BuffKind::Parried => (vec![BuffEffect::AttackSpeed(0.5)], data.duration),
|
||||
BuffKind::PotionSickness => (
|
||||
vec![BuffEffect::HealReduction {
|
||||
rate: data.strength,
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
};
|
||||
Buff {
|
||||
kind,
|
||||
data,
|
||||
cat_ids,
|
||||
time,
|
||||
delay: data.delay,
|
||||
effects,
|
||||
source,
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate how much time has elapsed since the buff was applied (if the
|
||||
/// buff has a finite duration, otherwise insufficient information
|
||||
/// exists to track that)
|
||||
pub fn elapsed(&self) -> Option<Duration> {
|
||||
self.data.duration.zip_with(self.time, |x, y| x - y)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(target_arch = "wasm32"))]
|
||||
@ -527,11 +564,18 @@ impl Buffs {
|
||||
.map(move |id| (*id, &self.buffs[id]))
|
||||
}
|
||||
|
||||
// Iterates through all active buffs (the most powerful buff of each kind)
|
||||
pub fn iter_active(&self) -> impl Iterator<Item = &Buff> + '_ {
|
||||
self.kinds
|
||||
.values()
|
||||
.filter_map(move |ids| self.buffs.get(&ids[0]))
|
||||
// Iterates through all active buffs (the most powerful buff of each
|
||||
// non-stacking kind, and all of the stacking ones)
|
||||
pub fn iter_active(&self) -> impl Iterator<Item = impl Iterator<Item = &Buff>> + '_ {
|
||||
self.kinds.iter().map(move |(kind, ids)| {
|
||||
if kind.stacks() {
|
||||
// Iterate stackable buffs in reverse order to show the timer of the soonest one
|
||||
// to expire
|
||||
Either::Left(ids.iter().filter_map(|id| self.buffs.get(id)).rev())
|
||||
} else {
|
||||
Either::Right(self.buffs.get(&ids[0]).into_iter())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Gets most powerful buff of a given kind
|
||||
|
@ -50,6 +50,7 @@ pub struct Stats {
|
||||
pub name: String,
|
||||
pub damage_reduction: f32,
|
||||
pub poise_reduction: f32,
|
||||
pub heal_multiplier: f32,
|
||||
pub max_health_modifiers: StatsModifier,
|
||||
pub move_speed_modifier: f32,
|
||||
pub attack_speed_modifier: f32,
|
||||
@ -63,6 +64,7 @@ impl Stats {
|
||||
name,
|
||||
damage_reduction: 0.0,
|
||||
poise_reduction: 0.0,
|
||||
heal_multiplier: 1.0,
|
||||
max_health_modifiers: StatsModifier::default(),
|
||||
move_speed_modifier: 1.0,
|
||||
attack_speed_modifier: 1.0,
|
||||
@ -79,6 +81,7 @@ impl Stats {
|
||||
pub fn reset_temp_modifiers(&mut self) {
|
||||
self.damage_reduction = 0.0;
|
||||
self.poise_reduction = 0.0;
|
||||
self.heal_multiplier = 1.0;
|
||||
self.max_health_modifiers = StatsModifier::default();
|
||||
self.move_speed_modifier = 1.0;
|
||||
self.attack_speed_modifier = 1.0;
|
||||
|
@ -65,6 +65,7 @@ impl CharacterBehavior for Data {
|
||||
BuffData {
|
||||
strength: self.static_data.buff_strength,
|
||||
duration: self.static_data.buff_duration,
|
||||
delay: None,
|
||||
},
|
||||
Vec::new(),
|
||||
BuffSource::Character { by: *data.uid },
|
||||
|
@ -146,7 +146,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
buff_change: BuffChange::Add(Buff::new(
|
||||
BuffKind::Ensnared,
|
||||
BuffData::new(1.0, Some(Duration::from_secs_f32(1.0))),
|
||||
BuffData::new(1.0, Some(Duration::from_secs_f32(1.0)), None),
|
||||
Vec::new(),
|
||||
BuffSource::World,
|
||||
)),
|
||||
@ -161,7 +161,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
buff_change: BuffChange::Add(Buff::new(
|
||||
BuffKind::Bleeding,
|
||||
BuffData::new(1.0, Some(Duration::from_secs_f32(6.0))),
|
||||
BuffData::new(1.0, Some(Duration::from_secs_f32(6.0)), None),
|
||||
Vec::new(),
|
||||
BuffSource::World,
|
||||
)),
|
||||
@ -179,7 +179,7 @@ impl<'a> System<'a> for Sys {
|
||||
entity,
|
||||
buff_change: BuffChange::Add(Buff::new(
|
||||
BuffKind::Burning,
|
||||
BuffData::new(20.0, None),
|
||||
BuffData::new(20.0, None, None),
|
||||
vec![BuffCategory::Natural],
|
||||
BuffSource::World,
|
||||
)),
|
||||
@ -245,31 +245,50 @@ impl<'a> System<'a> for Sys {
|
||||
|
||||
// Iterator over the lists of buffs by kind
|
||||
let buff_comp = &mut *buff_comp;
|
||||
for buff_ids in buff_comp.kinds.values() {
|
||||
// Get the strongest of this buff kind
|
||||
if let Some(buff) = buff_comp.buffs.get_mut(&buff_ids[0]) {
|
||||
// Get buff owner?
|
||||
let buff_owner = if let BuffSource::Character { by: owner } = buff.source {
|
||||
Some(owner)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut buff_kinds = buff_comp
|
||||
.kinds
|
||||
.iter()
|
||||
.map(|(kind, ids)| (*kind, ids.clone()))
|
||||
.collect::<Vec<(BuffKind, Vec<BuffId>)>>();
|
||||
buff_kinds.sort_by_key(|(kind, _)| !kind.affects_subsequent_buffs());
|
||||
for (buff_kind, buff_ids) in buff_kinds.into_iter() {
|
||||
let mut active_buff_ids = Vec::new();
|
||||
if buff_kind.stacks() {
|
||||
// Process all the buffs of this kind
|
||||
active_buff_ids = buff_ids;
|
||||
} else {
|
||||
// Only process the strongest of this buff kind
|
||||
active_buff_ids.push(buff_ids[0]);
|
||||
}
|
||||
for buff_id in active_buff_ids.into_iter() {
|
||||
if let Some(buff) = buff_comp.buffs.get_mut(&buff_id) {
|
||||
// Skip the effect of buffs whose start delay hasn't expired.
|
||||
if buff.delay.is_some() {
|
||||
continue;
|
||||
}
|
||||
// Get buff owner?
|
||||
let buff_owner = if let BuffSource::Character { by: owner } = buff.source {
|
||||
Some(owner)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// Now, execute the buff, based on it's delta
|
||||
for effect in &mut buff.effects {
|
||||
execute_effect(
|
||||
effect,
|
||||
buff.kind,
|
||||
buff.time,
|
||||
&read_data,
|
||||
&mut stat,
|
||||
health,
|
||||
energy,
|
||||
entity,
|
||||
buff_owner,
|
||||
&mut server_emitter,
|
||||
dt,
|
||||
);
|
||||
// Now, execute the buff, based on it's delta
|
||||
for effect in &mut buff.effects {
|
||||
execute_effect(
|
||||
effect,
|
||||
buff.kind,
|
||||
buff.time,
|
||||
&read_data,
|
||||
&mut stat,
|
||||
health,
|
||||
energy,
|
||||
entity,
|
||||
buff_owner,
|
||||
&mut server_emitter,
|
||||
dt,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -331,7 +350,7 @@ fn execute_effect(
|
||||
} else {
|
||||
(None, None)
|
||||
};
|
||||
let amount = match *kind {
|
||||
let mut amount = match *kind {
|
||||
ModifierKind::Additive => *accumulated,
|
||||
ModifierKind::Fractional => health.maximum() * *accumulated,
|
||||
};
|
||||
@ -343,6 +362,9 @@ fn execute_effect(
|
||||
DamageContributor::new(uid, read_data.groups.get(entity).cloned())
|
||||
})
|
||||
});
|
||||
if amount > 0.0 && matches!(buff_kind, BuffKind::Potion) {
|
||||
amount *= stat.heal_multiplier;
|
||||
}
|
||||
server_emitter.emit(ServerEvent::HealthChange {
|
||||
entity,
|
||||
change: HealthChange {
|
||||
@ -471,6 +493,9 @@ fn execute_effect(
|
||||
BuffEffect::PoiseReduction(pr) => {
|
||||
stat.poise_reduction = stat.poise_reduction.max(*pr).min(1.0);
|
||||
},
|
||||
BuffEffect::HealReduction { rate } => {
|
||||
stat.heal_multiplier *= 1.0 - *rate;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@ -483,6 +508,9 @@ fn tick_buff(id: u64, buff: &mut Buff, dt: f32, mut expire_buff: impl FnMut(u64)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if let Some(remaining_delay) = buff.delay {
|
||||
buff.delay = remaining_delay.checked_sub(Duration::from_secs_f32(dt));
|
||||
}
|
||||
if let Some(remaining_time) = &mut buff.time {
|
||||
if let Some(new_duration) = remaining_time.checked_sub(Duration::from_secs_f32(dt)) {
|
||||
// The buff still continues.
|
||||
|
@ -528,8 +528,14 @@ impl<'a> AgentData<'a> {
|
||||
controller: &mut Controller,
|
||||
relaxed: bool,
|
||||
) -> bool {
|
||||
// Wait for potion sickness to wear off if potions are less than 50% effective.
|
||||
let heal_multiplier = self.stats.map_or(1.0, |s| s.heal_multiplier);
|
||||
if heal_multiplier < 0.5 {
|
||||
return false;
|
||||
}
|
||||
let healing_value = |item: &Item| {
|
||||
let mut value = 0.0;
|
||||
let mut causes_potion_sickness = false;
|
||||
|
||||
if let ItemKind::Consumable { kind, effects, .. } = &*item.kind() {
|
||||
if matches!(kind, ConsumableKind::Drink)
|
||||
@ -547,11 +553,22 @@ impl<'a> AgentData<'a> {
|
||||
value += data.strength
|
||||
* data.duration.map_or(0.0, |d| d.as_secs() as f32);
|
||||
},
|
||||
Effect::Buff(BuffEffect { kind, .. })
|
||||
if matches!(kind, PotionSickness) =>
|
||||
{
|
||||
causes_potion_sickness = true;
|
||||
},
|
||||
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Prefer non-potion sources of healing when under at least one stack of potion
|
||||
// sickness, or when incurring potion sickness is unnecessary
|
||||
if causes_potion_sickness && (heal_multiplier < 1.0 || relaxed) {
|
||||
value *= 0.1;
|
||||
}
|
||||
value as i32
|
||||
};
|
||||
|
||||
|
@ -45,6 +45,7 @@ pub struct AgentData<'a> {
|
||||
pub active_abilities: &'a ActiveAbilities,
|
||||
pub combo: Option<&'a Combo>,
|
||||
pub buffs: Option<&'a Buffs>,
|
||||
pub stats: Option<&'a Stats>,
|
||||
pub poise: Option<&'a Poise>,
|
||||
pub cached_spatial_grid: &'a common::CachedSpatialGrid,
|
||||
pub msm: &'a MaterialStatManifest,
|
||||
|
@ -1477,7 +1477,7 @@ fn handle_spawn_campfire(
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::CampfireHeal,
|
||||
data: BuffData::new(0.02, Some(Duration::from_secs(1))),
|
||||
data: BuffData::new(0.02, Some(Duration::from_secs(1)), None),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
@ -1488,7 +1488,7 @@ fn handle_spawn_campfire(
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::Burning,
|
||||
data: BuffData::new(2.0, Some(Duration::from_secs(10))),
|
||||
data: BuffData::new(2.0, Some(Duration::from_secs(10)), None),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
@ -3520,7 +3520,7 @@ fn handle_apply_buff(
|
||||
if let (Some(buff), strength, duration) = parse_cmd_args!(args, String, f32, f64) {
|
||||
let strength = strength.unwrap_or(0.01);
|
||||
let duration = Duration::from_secs_f64(duration.unwrap_or(1.0));
|
||||
let buffdata = BuffData::new(strength, Some(duration));
|
||||
let buffdata = BuffData::new(strength, Some(duration), None);
|
||||
if buff != "all" {
|
||||
cast_buff(&buff, buffdata, server, target)
|
||||
} else {
|
||||
|
@ -285,7 +285,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::CampfireHeal,
|
||||
data: BuffData::new(0.02, Some(Duration::from_secs(1))),
|
||||
data: BuffData::new(0.02, Some(Duration::from_secs(1)), None),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
@ -296,7 +296,7 @@ pub fn handle_create_waypoint(server: &mut Server, pos: Vec3<f32>) {
|
||||
Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::Burning,
|
||||
data: BuffData::new(2.0, Some(Duration::from_secs(10))),
|
||||
data: BuffData::new(2.0, Some(Duration::from_secs(10)), None),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
|
@ -1306,7 +1306,7 @@ pub fn handle_parry_hook(server: &Server, defender: EcsEntity, attacker: Option<
|
||||
.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 data = buff::BuffData::new(1.0, Some(Duration::from_secs_f32(duration)), None);
|
||||
let source = if let Some(uid) = ecs.read_storage::<Uid>().get(defender) {
|
||||
BuffSource::Character { by: *uid }
|
||||
} else {
|
||||
|
@ -421,7 +421,7 @@ impl StateExt for State {
|
||||
.with(Auras::new(vec![Aura::new(
|
||||
AuraKind::Buff {
|
||||
kind: BuffKind::Invulnerability,
|
||||
data: BuffData::new(1.0, Some(Duration::from_secs(1))),
|
||||
data: BuffData::new(1.0, Some(Duration::from_secs(1)), None),
|
||||
category: BuffCategory::Natural,
|
||||
source: BuffSource::World,
|
||||
},
|
||||
|
@ -199,6 +199,7 @@ impl<'a> System<'a> for Sys {
|
||||
active_abilities,
|
||||
combo,
|
||||
buffs: read_data.buffs.get(entity),
|
||||
stats: read_data.stats.get(entity),
|
||||
cached_spatial_grid: &read_data.cached_spatial_grid,
|
||||
msm: &read_data.msm,
|
||||
poise: read_data.poises.get(entity),
|
||||
|
@ -106,7 +106,11 @@ pub fn localize_chat_message(
|
||||
tracing::error!("Player was killed by a positive buff!");
|
||||
"hud-outcome-mysterious"
|
||||
},
|
||||
BuffKind::Wet | BuffKind::Ensnared | BuffKind::Poisoned | BuffKind::Parried => {
|
||||
BuffKind::Wet
|
||||
| BuffKind::Ensnared
|
||||
| BuffKind::Poisoned
|
||||
| BuffKind::Parried
|
||||
| BuffKind::PotionSickness => {
|
||||
tracing::error!("Player was killed by a debuff that doesn't do damage!");
|
||||
"hud-outcome-mysterious"
|
||||
},
|
||||
|
@ -16,6 +16,7 @@ use conrod_core::{
|
||||
widget::{self, Button, Image, Rectangle, Text},
|
||||
widget_ids, Color, Colorable, Positionable, Sizeable, Widget, WidgetCommon,
|
||||
};
|
||||
|
||||
widget_ids! {
|
||||
struct Ids {
|
||||
align,
|
||||
@ -28,6 +29,8 @@ widget_ids! {
|
||||
debuffs[],
|
||||
debuff_timers[],
|
||||
buff_txts[],
|
||||
buff_multiplicities[],
|
||||
debuff_multiplicities[],
|
||||
}
|
||||
}
|
||||
|
||||
@ -87,6 +90,9 @@ pub enum Event {
|
||||
RemoveBuff(BuffKind),
|
||||
}
|
||||
|
||||
const MULTIPLICITY_COLOR: Color = TEXT_COLOR;
|
||||
const MULTIPLICITY_FONT_SIZE: u32 = 20;
|
||||
|
||||
impl<'a> Widget for BuffsBar<'a> {
|
||||
type Event = Vec<Event>;
|
||||
type State = State;
|
||||
@ -181,6 +187,17 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
if state.ids.debuff_timers.len() < debuff_count {
|
||||
state.update(|state| state.ids.debuff_timers.resize(debuff_count, gen));
|
||||
};
|
||||
if state.ids.buff_multiplicities.len() < 2 * buff_count {
|
||||
state.update(|state| state.ids.buff_multiplicities.resize(2 * buff_count, gen));
|
||||
};
|
||||
if state.ids.debuff_multiplicities.len() < 2 * debuff_count {
|
||||
state.update(|state| {
|
||||
state
|
||||
.ids
|
||||
.debuff_multiplicities
|
||||
.resize(2 * debuff_count, gen)
|
||||
});
|
||||
};
|
||||
|
||||
// Create Buff Widgets
|
||||
let mut buff_vec = state
|
||||
@ -189,16 +206,18 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(state.ids.buff_multiplicities.chunks(2))
|
||||
.zip(buff_icons.iter().filter(|info| info.is_buff))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort the buffs by kind
|
||||
buff_vec.sort_by_key(|((_id, _timer_id), buff)| std::cmp::Reverse(buff.kind));
|
||||
buff_vec
|
||||
.sort_by_key(|(((_id, _timer_id), _mult_id), buff)| std::cmp::Reverse(buff.kind));
|
||||
|
||||
buff_vec
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), buff))| {
|
||||
.for_each(|(i, (((id, timer_id), mult_id), buff))| {
|
||||
let max_duration = buff.kind.max_duration();
|
||||
let current_duration = buff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
@ -225,6 +244,20 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
},
|
||||
)
|
||||
.set(*id, ui);
|
||||
if buff.multiplicity() > 1 {
|
||||
Rectangle::fill_with([0.0, 0.0], MULTIPLICITY_COLOR.plain_contrast())
|
||||
.bottom_right_with_margins_on(*id, 1.0, 1.0)
|
||||
.wh_of(mult_id[1])
|
||||
.graphics_for(*id)
|
||||
.set(mult_id[0], ui);
|
||||
Text::new(&format!("{}", buff.multiplicity()))
|
||||
.middle_of(mult_id[0])
|
||||
.graphics_for(*id)
|
||||
.font_size(self.fonts.cyri.scale(MULTIPLICITY_FONT_SIZE))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(MULTIPLICITY_COLOR)
|
||||
.set(mult_id[1], ui);
|
||||
}
|
||||
// Create Buff tooltip
|
||||
let (title, desc_txt) = buff.kind.title_description(localized_strings);
|
||||
let remaining_time = buff.get_buff_time();
|
||||
@ -259,16 +292,17 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.debuff_timers.iter().copied())
|
||||
.zip(state.ids.debuff_multiplicities.chunks(2))
|
||||
.zip(buff_icons.iter().filter(|info| !info.is_buff))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort the debuffs by kind
|
||||
debuff_vec.sort_by_key(|((_id, _timer_id), debuff)| debuff.kind);
|
||||
debuff_vec.sort_by_key(|(((_id, _timer_id), _mult_id), debuff)| debuff.kind);
|
||||
|
||||
debuff_vec
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), debuff))| {
|
||||
.for_each(|(i, (((id, timer_id), mult_id), debuff))| {
|
||||
let max_duration = debuff.kind.max_duration();
|
||||
let current_duration = debuff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
@ -295,6 +329,20 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
},
|
||||
)
|
||||
.set(*id, ui);
|
||||
if debuff.multiplicity() > 1 {
|
||||
Rectangle::fill_with([0.0, 0.0], MULTIPLICITY_COLOR.plain_contrast())
|
||||
.bottom_right_with_margins_on(*id, 1.0, 1.0)
|
||||
.wh_of(mult_id[1])
|
||||
.graphics_for(*id)
|
||||
.set(mult_id[0], ui);
|
||||
Text::new(&format!("{}", debuff.multiplicity()))
|
||||
.middle_of(mult_id[0])
|
||||
.graphics_for(*id)
|
||||
.font_size(self.fonts.cyri.scale(MULTIPLICITY_FONT_SIZE))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(MULTIPLICITY_COLOR)
|
||||
.set(mult_id[1], ui);
|
||||
}
|
||||
// Create Debuff tooltip
|
||||
let (title, desc_txt) = debuff.kind.title_description(localized_strings);
|
||||
let remaining_time = debuff.get_buff_time();
|
||||
@ -334,6 +382,9 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
if state.ids.buff_txts.len() < buff_count {
|
||||
state.update(|state| state.ids.buff_txts.resize(buff_count, gen));
|
||||
};
|
||||
if state.ids.buff_multiplicities.len() < 2 * buff_count {
|
||||
state.update(|state| state.ids.buff_multiplicities.resize(2 * buff_count, gen));
|
||||
};
|
||||
|
||||
// Create Buff Widgets
|
||||
|
||||
@ -344,16 +395,15 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(state.ids.buff_txts.iter().copied())
|
||||
.zip(state.ids.buff_multiplicities.chunks(2))
|
||||
.zip(buff_icons.iter())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Sort the buffs by kind
|
||||
buff_vec.sort_by_key(|((_id, _timer_id), txt_id)| std::cmp::Reverse(txt_id.kind));
|
||||
|
||||
buff_vec
|
||||
.iter()
|
||||
.enumerate()
|
||||
.for_each(|(i, (((id, timer_id), txt_id), buff))| {
|
||||
buff_vec.iter().enumerate().for_each(
|
||||
|(i, ((((id, timer_id), txt_id), mult_id), buff))| {
|
||||
let max_duration = buff.kind.max_duration();
|
||||
let current_duration = buff.dur;
|
||||
// Percentage to determine which frame of the timer overlay is displayed
|
||||
@ -380,6 +430,20 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
},
|
||||
)
|
||||
.set(*id, ui);
|
||||
if buff.multiplicity() > 1 {
|
||||
Rectangle::fill_with([0.0, 0.0], MULTIPLICITY_COLOR.plain_contrast())
|
||||
.bottom_right_with_margins_on(*id, 1.0, 1.0)
|
||||
.wh_of(mult_id[1])
|
||||
.graphics_for(*id)
|
||||
.set(mult_id[0], ui);
|
||||
Text::new(&format!("{}", buff.multiplicity()))
|
||||
.middle_of(mult_id[0])
|
||||
.graphics_for(*id)
|
||||
.font_size(self.fonts.cyri.scale(MULTIPLICITY_FONT_SIZE))
|
||||
.font_id(self.fonts.cyri.conrod_id)
|
||||
.color(MULTIPLICITY_COLOR)
|
||||
.set(mult_id[1], ui);
|
||||
}
|
||||
// Create Buff tooltip
|
||||
let (title, desc_txt) = buff.kind.title_description(localized_strings);
|
||||
let remaining_time = buff.get_buff_time();
|
||||
@ -420,7 +484,8 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.graphics_for(*timer_id)
|
||||
.color(TEXT_COLOR)
|
||||
.set(*txt_id, ui);
|
||||
});
|
||||
},
|
||||
);
|
||||
}
|
||||
event
|
||||
}
|
||||
|
@ -697,6 +697,7 @@ image_ids! {
|
||||
debuff_ensnared_0: "voxygen.element.de_buffs.debuff_ensnared_0",
|
||||
debuff_poisoned_0: "voxygen.element.de_buffs.debuff_poisoned_0",
|
||||
debuff_parried_0: "voxygen.element.de_buffs.debuff_parried_0",
|
||||
debuff_potionsickness_0: "voxygen.element.de_buffs.debuff_potionsickness_0",
|
||||
|
||||
// Animation Frames
|
||||
// Buff Frame
|
||||
|
@ -452,10 +452,16 @@ impl<W: Positionable> Position for W {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub enum BuffIconKind<'a> {
|
||||
Buff { kind: BuffKind, data: BuffData },
|
||||
Ability { ability_id: &'a str },
|
||||
Buff {
|
||||
kind: BuffKind,
|
||||
data: BuffData,
|
||||
multiplicity: usize,
|
||||
},
|
||||
Ability {
|
||||
ability_id: &'a str,
|
||||
},
|
||||
}
|
||||
|
||||
impl<'a> BuffIconKind<'a> {
|
||||
@ -478,7 +484,11 @@ impl<'a> BuffIconKind<'a> {
|
||||
localized_strings: &'b Localization,
|
||||
) -> (Cow<'b, str>, Cow<'b, str>) {
|
||||
match self {
|
||||
Self::Buff { kind, data } => (
|
||||
Self::Buff {
|
||||
kind,
|
||||
data,
|
||||
multiplicity: _,
|
||||
} => (
|
||||
get_buff_title(*kind, localized_strings),
|
||||
get_buff_desc(*kind, *data, localized_strings),
|
||||
),
|
||||
@ -540,7 +550,7 @@ impl<'a> PartialEq for BuffIconKind<'a> {
|
||||
|
||||
impl<'a> Eq for BuffIconKind<'a> {}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BuffIcon<'a> {
|
||||
kind: BuffIconKind<'a>,
|
||||
is_buff: bool,
|
||||
@ -548,6 +558,13 @@ pub struct BuffIcon<'a> {
|
||||
}
|
||||
|
||||
impl<'a> BuffIcon<'a> {
|
||||
pub fn multiplicity(&self) -> usize {
|
||||
match self.kind {
|
||||
BuffIconKind::Buff { multiplicity, .. } => multiplicity,
|
||||
BuffIconKind::Ability { .. } => 1,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_buff_time(&self) -> String {
|
||||
if let Some(dur) = self.dur {
|
||||
format!("{:.0}s", dur.as_secs_f32())
|
||||
@ -559,7 +576,7 @@ impl<'a> BuffIcon<'a> {
|
||||
pub fn icons_vec(buffs: &comp::Buffs, char_state: &comp::CharacterState) -> Vec<Self> {
|
||||
buffs
|
||||
.iter_active()
|
||||
.map(BuffIcon::from_buff)
|
||||
.filter_map(BuffIcon::from_buffs)
|
||||
.chain(BuffIcon::from_char_state(char_state).into_iter())
|
||||
.collect::<Vec<_>>()
|
||||
}
|
||||
@ -581,15 +598,20 @@ impl<'a> BuffIcon<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_buff(buff: &comp::Buff) -> Self {
|
||||
Self {
|
||||
fn from_buffs<'b, I: Iterator<Item = &'b comp::Buff>>(buffs: I) -> Option<Self> {
|
||||
let (buff, count) = buffs.fold((None, 0), |(strongest, count), buff| {
|
||||
(strongest.or(Some(buff)), count + 1)
|
||||
});
|
||||
let buff = buff?;
|
||||
Some(Self {
|
||||
kind: BuffIconKind::Buff {
|
||||
kind: buff.kind,
|
||||
data: buff.data,
|
||||
multiplicity: count,
|
||||
},
|
||||
is_buff: buff.kind.is_buff(),
|
||||
dur: buff.time,
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -4769,6 +4791,7 @@ pub fn get_buff_image(buff: BuffKind, imgs: &Imgs) -> conrod_core::image::Id {
|
||||
BuffKind::Ensnared => imgs.debuff_ensnared_0,
|
||||
BuffKind::Poisoned => imgs.debuff_poisoned_0,
|
||||
BuffKind::Parried => imgs.debuff_parried_0,
|
||||
BuffKind::PotionSickness => imgs.debuff_potionsickness_0,
|
||||
}
|
||||
}
|
||||
|
||||
@ -4801,6 +4824,7 @@ pub fn get_buff_title(buff: BuffKind, localized_strings: &Localization) -> Cow<s
|
||||
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"),
|
||||
BuffKind::PotionSickness { .. } => localized_strings.get_msg("buff-title-potionsickness"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -4837,6 +4861,7 @@ pub fn get_buff_desc(buff: BuffKind, data: BuffData, localized_strings: &Localiz
|
||||
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"),
|
||||
BuffKind::PotionSickness { .. } => localized_strings.get_msg("buff-desc-potionsickness"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -172,6 +172,11 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> Vec<String> {
|
||||
"strength" => format_float(strength),
|
||||
})
|
||||
},
|
||||
BuffKind::PotionSickness => {
|
||||
i18n.get_msg_ctx("buff-stat-potionsickness", &i18n::fluent_args! {
|
||||
"strength" => format_float(strength * 100.0),
|
||||
})
|
||||
},
|
||||
BuffKind::Invulnerability => i18n.get_msg("buff-stat-invulnerability"),
|
||||
BuffKind::Bleeding
|
||||
| BuffKind::Burning
|
||||
@ -199,7 +204,8 @@ pub fn consumable_desc(effects: &[Effect], i18n: &Localization) -> Vec<String> {
|
||||
}),
|
||||
BuffKind::IncreaseMaxEnergy
|
||||
| BuffKind::IncreaseMaxHealth
|
||||
| BuffKind::Invulnerability => {
|
||||
| BuffKind::Invulnerability
|
||||
| BuffKind::PotionSickness => {
|
||||
i18n.get_msg_ctx("buff-text-for_seconds", &i18n::fluent_args! {
|
||||
"dur_secs" => dur_secs
|
||||
})
|
||||
|
@ -92,6 +92,7 @@ pub enum ParticleMode {
|
||||
Lightning = 38,
|
||||
Steam = 39,
|
||||
BarrelOrgan = 40,
|
||||
PotionSickness = 41,
|
||||
}
|
||||
|
||||
impl ParticleMode {
|
||||
|
@ -1148,17 +1148,18 @@ impl ParticleMgr {
|
||||
let time = state.get_time();
|
||||
let mut rng = thread_rng();
|
||||
|
||||
for (interp, pos, buffs, body) in (
|
||||
for (interp, pos, buffs, body, ori) in (
|
||||
ecs.read_storage::<Interpolated>().maybe(),
|
||||
&ecs.read_storage::<Pos>(),
|
||||
&ecs.read_storage::<comp::Buffs>(),
|
||||
&ecs.read_storage::<Body>(),
|
||||
&ecs.read_storage::<Ori>(),
|
||||
)
|
||||
.join()
|
||||
{
|
||||
let pos = interp.map_or(pos.0, |i| i.pos);
|
||||
|
||||
for (buff_kind, _) in buffs.kinds.iter() {
|
||||
for (buff_kind, buff_ids) in buffs.kinds.iter() {
|
||||
use buff::BuffKind;
|
||||
match buff_kind {
|
||||
BuffKind::Cursed | BuffKind::Burning => {
|
||||
@ -1180,7 +1181,7 @@ impl ParticleMgr {
|
||||
Particle::new_directed(
|
||||
Duration::from_secs(1),
|
||||
time,
|
||||
if matches!(buff_kind, buff::BuffKind::Cursed) {
|
||||
if matches!(buff_kind, BuffKind::Cursed) {
|
||||
ParticleMode::CultistFlame
|
||||
} else {
|
||||
ParticleMode::FlameThrower
|
||||
@ -1191,6 +1192,46 @@ impl ParticleMgr {
|
||||
},
|
||||
);
|
||||
},
|
||||
BuffKind::PotionSickness => {
|
||||
let mut multiplicity = 0;
|
||||
// Only show particles for potion sickness at the beginning, after the
|
||||
// drinking animation finishes
|
||||
if buff_ids
|
||||
.iter()
|
||||
.filter_map(|id| buffs.buffs.get(id))
|
||||
.any(|buff| {
|
||||
matches!(buff.elapsed(), Some(dur) if Duration::from_secs(1) <= dur && dur <= Duration::from_secs_f32(1.5))
|
||||
})
|
||||
{
|
||||
multiplicity = 1;
|
||||
}
|
||||
self.particles.resize_with(
|
||||
self.particles.len()
|
||||
+ multiplicity
|
||||
* usize::from(
|
||||
self.scheduler.heartbeats(Duration::from_millis(25)),
|
||||
),
|
||||
|| {
|
||||
let start_pos = pos + Vec3::unit_z() * body.eye_height();
|
||||
let (radius, theta) =
|
||||
(rng.gen_range(0.0f32..1.0).sqrt(), rng.gen_range(0.0..TAU));
|
||||
let end_pos = pos
|
||||
+ *ori.look_dir()
|
||||
+ Vec3::<f32>::new(
|
||||
radius * theta.cos(),
|
||||
radius * theta.sin(),
|
||||
0.0,
|
||||
) * 0.25;
|
||||
Particle::new_directed(
|
||||
Duration::from_secs(1),
|
||||
time,
|
||||
ParticleMode::PotionSickness,
|
||||
start_pos,
|
||||
end_pos,
|
||||
)
|
||||
},
|
||||
);
|
||||
},
|
||||
BuffKind::Frenzied => {
|
||||
self.particles.resize_with(
|
||||
self.particles.len()
|
||||
|
Loading…
Reference in New Issue
Block a user