mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Transitioned buff storage from a vec to a hashmap. Addressed other comments. Only continuous buff effects are handled right now.
This commit is contained in:
parent
337cf6e137
commit
f60985d733
11
Cargo.lock
generated
11
Cargo.lock
generated
@ -2037,6 +2037,15 @@ dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inline_tweak"
|
||||
version = "1.0.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7033e97b20277cc0d043226d1940fa7719ff08d2305d1fc7421e53066d00eb4b"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inotify"
|
||||
version = "0.8.3"
|
||||
@ -4957,6 +4966,7 @@ dependencies = [
|
||||
"guillotiere",
|
||||
"hashbrown 0.7.2",
|
||||
"image",
|
||||
"inline_tweak",
|
||||
"itertools",
|
||||
"native-dialog",
|
||||
"num 0.2.1",
|
||||
@ -4988,6 +4998,7 @@ name = "veloren-voxygen-anim"
|
||||
version = "0.7.0"
|
||||
dependencies = [
|
||||
"find_folder",
|
||||
"inline_tweak",
|
||||
"lazy_static",
|
||||
"libloading 0.6.3",
|
||||
"notify",
|
||||
|
@ -2,37 +2,46 @@ use crate::sync::Uid;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use specs::{Component, FlaggedStorage};
|
||||
use specs_idvs::IdvStorage;
|
||||
use std::time::Duration;
|
||||
use std::{collections::HashMap, time::Duration};
|
||||
|
||||
/// De/buff Kind.
|
||||
/// This is used to determine what effects a buff will have, as well as
|
||||
/// determine the strength and duration of the buff effects using the internal
|
||||
/// values
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffKind {
|
||||
/// Restores health/time for some period
|
||||
Regeneration {
|
||||
strength: f32,
|
||||
duration: Option<Duration>,
|
||||
},
|
||||
Regeneration,
|
||||
/// Lowers health over time for some duration
|
||||
Bleeding {
|
||||
strength: f32,
|
||||
duration: Option<Duration>,
|
||||
},
|
||||
Bleeding,
|
||||
/// Prefixes an entity's name with "Cursed"
|
||||
/// Currently placeholder buff to show other stuff is possible
|
||||
Cursed { duration: Option<Duration> },
|
||||
Cursed,
|
||||
}
|
||||
|
||||
impl BuffKind {
|
||||
// Checks if buff is buff or debuff
|
||||
pub fn is_buff(self) -> bool {
|
||||
match self {
|
||||
BuffKind::Regeneration { .. } => true,
|
||||
BuffKind::Bleeding { .. } => false,
|
||||
BuffKind::Cursed { .. } => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Struct used to store data relevant to a buff
|
||||
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
|
||||
pub struct BuffData {
|
||||
pub strength: f32,
|
||||
pub duration: Option<Duration>,
|
||||
}
|
||||
|
||||
/// De/buff category ID.
|
||||
/// Similar to `BuffKind`, but to mark a category (for more generic usage, like
|
||||
/// positive/negative buffs).
|
||||
#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffCategoryId {
|
||||
// Buff and debuff get added in builder function based off of the buff kind
|
||||
Debuff,
|
||||
Buff,
|
||||
pub enum BuffCategory {
|
||||
Natural,
|
||||
Physical,
|
||||
Magical,
|
||||
@ -62,7 +71,8 @@ pub enum BuffEffect {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct Buff {
|
||||
pub kind: BuffKind,
|
||||
pub cat_ids: Vec<BuffCategoryId>,
|
||||
pub data: BuffData,
|
||||
pub cat_ids: Vec<BuffCategory>,
|
||||
pub time: Option<Duration>,
|
||||
pub effects: Vec<BuffEffect>,
|
||||
pub source: BuffSource,
|
||||
@ -70,27 +80,68 @@ pub struct Buff {
|
||||
|
||||
/// Information about whether buff addition or removal was requested.
|
||||
/// This to implement "on_add" and "on_remove" hooks for constant buffs.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum BuffChange {
|
||||
/// Adds this buff.
|
||||
Add(Buff),
|
||||
/// Removes all buffs with this ID.
|
||||
RemoveByKind(BuffKind),
|
||||
/// Removes all buffs with this ID, but not debuffs.
|
||||
RemoveFromClient(BuffKind),
|
||||
RemoveFromController(BuffKind),
|
||||
/// Removes buffs of these indices (first vec is for active buffs, second is
|
||||
/// for inactive buffs), should only be called when buffs expire
|
||||
RemoveExpiredByIndex(Vec<usize>, Vec<usize>),
|
||||
RemoveById(Vec<BuffId>),
|
||||
/// Removes buffs of these categories (first vec is of categories of which
|
||||
/// all are required, second vec is of categories of which at least one is
|
||||
/// required, third vec is of categories that will not be removed)
|
||||
RemoveByCategory {
|
||||
required: Vec<BuffCategoryId>,
|
||||
optional: Vec<BuffCategoryId>,
|
||||
blacklisted: Vec<BuffCategoryId>,
|
||||
all_required: Vec<BuffCategory>,
|
||||
any_required: Vec<BuffCategory>,
|
||||
none_required: Vec<BuffCategory>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Buff {
|
||||
/// Builder function for buffs
|
||||
pub fn new(
|
||||
kind: BuffKind,
|
||||
data: BuffData,
|
||||
cat_ids: Vec<BuffCategory>,
|
||||
source: BuffSource,
|
||||
) -> Self {
|
||||
let (effects, time) = match kind {
|
||||
BuffKind::Bleeding => (
|
||||
vec![BuffEffect::HealthChangeOverTime {
|
||||
rate: -data.strength,
|
||||
accumulated: 0.0,
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
BuffKind::Regeneration => (
|
||||
vec![BuffEffect::HealthChangeOverTime {
|
||||
rate: data.strength,
|
||||
accumulated: 0.0,
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
BuffKind::Cursed => (
|
||||
vec![BuffEffect::NameChange {
|
||||
prefix: String::from("Cursed "),
|
||||
}],
|
||||
data.duration,
|
||||
),
|
||||
};
|
||||
Buff {
|
||||
kind,
|
||||
data,
|
||||
cat_ids,
|
||||
time,
|
||||
effects,
|
||||
source,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Source of the de/buff
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffSource {
|
||||
@ -121,65 +172,84 @@ pub enum BuffSource {
|
||||
/// would be probably an undesired effect).
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Buffs {
|
||||
/// Active de/buffs.
|
||||
pub active_buffs: Vec<Buff>,
|
||||
/// Inactive de/buffs (used so that only 1 buff of a particular type is
|
||||
/// active at any time)
|
||||
pub inactive_buffs: Vec<Buff>,
|
||||
id_counter: u64,
|
||||
pub kinds: HashMap<BuffKind, Vec<BuffId>>,
|
||||
pub buffs: HashMap<BuffId, Buff>,
|
||||
}
|
||||
|
||||
impl Buff {
|
||||
/// Builder function for buffs
|
||||
pub fn new(kind: BuffKind, cat_ids: Vec<BuffCategoryId>, source: BuffSource) -> Self {
|
||||
let mut cat_ids = cat_ids;
|
||||
let (effects, time) = match kind {
|
||||
BuffKind::Bleeding { strength, duration } => {
|
||||
cat_ids.push(BuffCategoryId::Debuff);
|
||||
(
|
||||
vec![BuffEffect::HealthChangeOverTime {
|
||||
rate: -strength,
|
||||
accumulated: 0.0,
|
||||
}],
|
||||
duration,
|
||||
)
|
||||
},
|
||||
BuffKind::Regeneration { strength, duration } => {
|
||||
cat_ids.push(BuffCategoryId::Buff);
|
||||
(
|
||||
vec![BuffEffect::HealthChangeOverTime {
|
||||
rate: strength,
|
||||
accumulated: 0.0,
|
||||
}],
|
||||
duration,
|
||||
)
|
||||
},
|
||||
BuffKind::Cursed { duration } => {
|
||||
cat_ids.push(BuffCategoryId::Debuff);
|
||||
(
|
||||
vec![BuffEffect::NameChange {
|
||||
prefix: String::from("Cursed "),
|
||||
}],
|
||||
duration,
|
||||
)
|
||||
},
|
||||
};
|
||||
assert_eq!(
|
||||
cat_ids
|
||||
.iter()
|
||||
.any(|cat| *cat == BuffCategoryId::Buff || *cat == BuffCategoryId::Debuff),
|
||||
true,
|
||||
"Buff must have either buff or debuff category."
|
||||
);
|
||||
Buff {
|
||||
kind,
|
||||
cat_ids,
|
||||
time,
|
||||
effects,
|
||||
source,
|
||||
impl Buffs {
|
||||
fn sort_kind(&mut self, kind: BuffKind) {
|
||||
if let Some(buff_order) = self.kinds.get_mut(&kind) {
|
||||
if buff_order.len() == 0 {
|
||||
self.kinds.remove(&kind);
|
||||
} else {
|
||||
let buffs = &self.buffs;
|
||||
buff_order.sort_by(|a, b| {
|
||||
buffs[&b]
|
||||
.data
|
||||
.strength
|
||||
.partial_cmp(&buffs[&a].data.strength)
|
||||
.unwrap()
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_kind(&mut self, kind: BuffKind) {
|
||||
if let Some(buff_ids) = self.kinds.get_mut(&kind) {
|
||||
for id in buff_ids {
|
||||
self.buffs.remove(id);
|
||||
}
|
||||
self.kinds.remove(&kind);
|
||||
}
|
||||
self.sort_kind(kind);
|
||||
}
|
||||
|
||||
pub fn force_insert(&mut self, id: BuffId, buff: Buff) -> BuffId {
|
||||
let kind = buff.kind;
|
||||
self.kinds.entry(kind).or_default().push(id);
|
||||
self.buffs.insert(id, buff);
|
||||
self.sort_kind(kind);
|
||||
id
|
||||
}
|
||||
|
||||
pub fn insert(&mut self, buff: Buff) -> BuffId {
|
||||
self.id_counter += 1;
|
||||
self.force_insert(self.id_counter, buff)
|
||||
}
|
||||
|
||||
// Iterate through buffs of a given kind in effect order (most powerful first)
|
||||
pub fn iter_kind(&self, kind: BuffKind) -> impl Iterator<Item = (BuffId, &Buff)> + '_ {
|
||||
self.kinds
|
||||
.get(&kind)
|
||||
.map(|ids| ids.iter())
|
||||
.unwrap_or((&[]).iter())
|
||||
.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()
|
||||
.map(move |ids| self.buffs.get(&ids[0]))
|
||||
.filter(|buff| buff.is_some())
|
||||
.map(|buff| buff.unwrap())
|
||||
}
|
||||
|
||||
// Gets most powerful buff of a given kind
|
||||
// pub fn get_active_kind(&self, kind: BuffKind) -> Buff
|
||||
|
||||
pub fn remove(&mut self, buff_id: BuffId) {
|
||||
let kind = self.buffs.remove(&buff_id).unwrap().kind;
|
||||
self.kinds
|
||||
.get_mut(&kind)
|
||||
.map(|ids| ids.retain(|id| *id != buff_id));
|
||||
self.sort_kind(kind);
|
||||
}
|
||||
}
|
||||
|
||||
pub type BuffId = u64;
|
||||
|
||||
impl Component for Buffs {
|
||||
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
||||
}
|
||||
|
@ -32,7 +32,9 @@ pub use body::{
|
||||
biped_large, bird_medium, bird_small, dragon, fish_medium, fish_small, golem, humanoid, object,
|
||||
quadruped_low, quadruped_medium, quadruped_small, theropod, AllBodies, Body, BodyData,
|
||||
};
|
||||
pub use buff::{Buff, BuffCategoryId, BuffChange, BuffEffect, BuffKind, BuffSource, Buffs};
|
||||
pub use buff::{
|
||||
Buff, BuffCategory, BuffChange, BuffData, BuffEffect, BuffId, BuffKind, BuffSource, Buffs,
|
||||
};
|
||||
pub use character_state::{Attacking, CharacterState, StateUpdate};
|
||||
pub use chat::{
|
||||
ChatMode, ChatMsg, ChatType, Faction, SpeechBubble, SpeechBubbleType, UnresolvedChatMsg,
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
comp::{
|
||||
BuffCategoryId, BuffChange, BuffEffect, BuffSource, Buffs, HealthChange, HealthSource,
|
||||
Stats,
|
||||
BuffCategory, BuffChange, BuffEffect, BuffId, BuffSource, Buffs, HealthChange,
|
||||
HealthSource, Stats,
|
||||
},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::DeltaTime,
|
||||
@ -25,108 +25,84 @@ impl<'a> System<'a> for Sys {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
// Set to false to avoid spamming server
|
||||
buffs.set_event_emission(false);
|
||||
for (buff_comp, uid) in (&mut buffs, &uids).join() {
|
||||
let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) =
|
||||
(Vec::<usize>::new(), Vec::<usize>::new());
|
||||
for (i, active_buff) in buff_comp.active_buffs.iter_mut().enumerate() {
|
||||
for (buff_comp, uid, stat) in (&mut buffs, &uids, &stats).join() {
|
||||
let mut expired_buffs = Vec::<BuffId>::new();
|
||||
for (id, buff) in buff_comp.buffs.iter_mut() {
|
||||
// Tick the buff and subtract delta from it
|
||||
if let Some(remaining_time) = &mut active_buff.time {
|
||||
let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0));
|
||||
if new_duration.is_some() {
|
||||
if let Some(remaining_time) = &mut buff.time {
|
||||
if let Some(new_duration) =
|
||||
remaining_time.checked_sub(Duration::from_secs_f32(dt.0))
|
||||
{
|
||||
// The buff still continues.
|
||||
*remaining_time -= Duration::from_secs_f32(dt.0);
|
||||
*remaining_time = new_duration;
|
||||
} else {
|
||||
// The buff has expired.
|
||||
// Remove it.
|
||||
active_buff_indices_for_removal.push(i);
|
||||
active_buff.time = Some(Duration::default());
|
||||
};
|
||||
expired_buffs.push(*id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (i, inactive_buff) in buff_comp.inactive_buffs.iter_mut().enumerate() {
|
||||
// Tick the buff and subtract delta from it
|
||||
if let Some(remaining_time) = &mut inactive_buff.time {
|
||||
let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0));
|
||||
if new_duration.is_some() {
|
||||
// The buff still continues.
|
||||
*remaining_time -= Duration::from_secs_f32(dt.0);
|
||||
for buff_ids in buff_comp.kinds.values() {
|
||||
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 {
|
||||
// The buff has expired.
|
||||
// Remove it.
|
||||
inactive_buff_indices_for_removal.push(i);
|
||||
inactive_buff.time = Some(Duration::default());
|
||||
None
|
||||
};
|
||||
// Now, execute the buff, based on it's delta
|
||||
for effect in &mut buff.effects {
|
||||
match effect {
|
||||
// Only add an effect here if it is continuous or it is not immediate
|
||||
BuffEffect::HealthChangeOverTime { rate, accumulated } => {
|
||||
*accumulated += *rate * dt.0;
|
||||
// Apply damage only once a second (with a minimum of 1 damage), or
|
||||
// when a buff is removed
|
||||
if accumulated.abs() > rate.abs().max(10.0)
|
||||
|| buff.time.map_or(false, |dur| dur == Duration::default())
|
||||
{
|
||||
let cause = if *accumulated > 0.0 {
|
||||
HealthSource::Healing { by: buff_owner }
|
||||
} else {
|
||||
HealthSource::Buff { owner: buff_owner }
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid,
|
||||
change: HealthChange {
|
||||
amount: *accumulated as i32,
|
||||
cause,
|
||||
},
|
||||
});
|
||||
*accumulated = 0.0;
|
||||
};
|
||||
},
|
||||
BuffEffect::NameChange { .. } => {},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !active_buff_indices_for_removal.is_empty()
|
||||
|| !inactive_buff_indices_for_removal.is_empty()
|
||||
{
|
||||
// Remove buffs that expire
|
||||
if !expired_buffs.is_empty() {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
uid: *uid,
|
||||
buff_change: BuffChange::RemoveExpiredByIndex(
|
||||
active_buff_indices_for_removal,
|
||||
inactive_buff_indices_for_removal,
|
||||
),
|
||||
buff_change: BuffChange::RemoveById(expired_buffs),
|
||||
});
|
||||
}
|
||||
}
|
||||
// Set back to true after timer decrement
|
||||
buffs.set_event_emission(true);
|
||||
for (uid, stat, mut buffs) in (&uids, &stats, &mut buffs.restrict_mut()).join() {
|
||||
let buff_comp = buffs.get_mut_unchecked();
|
||||
// Tick all de/buffs on a Buffs component.
|
||||
for active_buff in buff_comp.active_buffs.iter_mut() {
|
||||
// Get buff owner
|
||||
let buff_owner = if let BuffSource::Character { by: owner } = active_buff.source {
|
||||
Some(owner)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
// Now, execute the buff, based on it's delta
|
||||
for effect in &mut active_buff.effects {
|
||||
match effect {
|
||||
// Only add an effect here if it is continuous or it is not immediate
|
||||
BuffEffect::HealthChangeOverTime { rate, accumulated } => {
|
||||
*accumulated += *rate * dt.0;
|
||||
// Apply damage only once a second (with a minimum of 1 damage), or when
|
||||
// a buff is removed
|
||||
if accumulated.abs() > rate.abs().max(10.0)
|
||||
|| active_buff
|
||||
.time
|
||||
.map_or(false, |dur| dur == Duration::default())
|
||||
{
|
||||
let cause = if *accumulated > 0.0 {
|
||||
HealthSource::Healing { by: buff_owner }
|
||||
} else {
|
||||
HealthSource::Buff { owner: buff_owner }
|
||||
};
|
||||
server_emitter.emit(ServerEvent::Damage {
|
||||
uid: *uid,
|
||||
change: HealthChange {
|
||||
amount: *accumulated as i32,
|
||||
cause,
|
||||
},
|
||||
});
|
||||
*accumulated = 0.0;
|
||||
};
|
||||
},
|
||||
BuffEffect::NameChange { .. } => {},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Remove stats that don't persist on death
|
||||
if stat.is_dead {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
uid: *uid,
|
||||
buff_change: BuffChange::RemoveByCategory {
|
||||
required: vec![],
|
||||
optional: vec![],
|
||||
blacklisted: vec![BuffCategoryId::PersistOnDeath],
|
||||
all_required: vec![],
|
||||
any_required: vec![],
|
||||
none_required: vec![BuffCategory::PersistOnDeath],
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
buffs.set_event_emission(true);
|
||||
}
|
||||
}
|
||||
|
@ -158,11 +158,12 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
uid: *uid_b,
|
||||
buff_change: buff::BuffChange::Add(buff::Buff::new(
|
||||
buff::BuffKind::Bleeding {
|
||||
buff::BuffKind::Bleeding,
|
||||
buff::BuffData {
|
||||
strength: attack.base_damage as f32 / 10.0,
|
||||
duration: Some(Duration::from_secs(10)),
|
||||
},
|
||||
vec![buff::BuffCategoryId::Physical],
|
||||
vec![buff::BuffCategory::Physical],
|
||||
buff::BuffSource::Character { by: *uid },
|
||||
)),
|
||||
});
|
||||
|
@ -86,7 +86,7 @@ impl<'a> System<'a> for Sys {
|
||||
ControlEvent::RemoveBuff(buff_id) => {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
uid: *uid,
|
||||
buff_change: BuffChange::RemoveFromClient(buff_id),
|
||||
buff_change: BuffChange::RemoveFromController(buff_id),
|
||||
});
|
||||
},
|
||||
ControlEvent::Unmount => server_emitter.emit(ServerEvent::Unmount(entity)),
|
||||
|
@ -24,7 +24,6 @@ use common::{
|
||||
use comp::item::Reagent;
|
||||
use rand::prelude::*;
|
||||
use specs::{join::Join, saveload::MarkerAllocator, Entity as EcsEntity, WorldExt};
|
||||
use std::mem::discriminant;
|
||||
use tracing::error;
|
||||
use vek::Vec3;
|
||||
|
||||
@ -706,105 +705,31 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange)
|
||||
if let Some(entity) = ecs.entity_from_uid(uid.into()) {
|
||||
if let Some(buffs) = buffs_all.get_mut(entity) {
|
||||
let mut stats = ecs.write_storage::<comp::Stats>();
|
||||
let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) =
|
||||
(Vec::new(), Vec::new());
|
||||
use buff::BuffChange;
|
||||
match buff_change {
|
||||
BuffChange::Add(new_buff) => {
|
||||
if buffs.active_buffs.is_empty() {
|
||||
add_buff_effects(new_buff.clone(), stats.get_mut(entity));
|
||||
buffs.active_buffs.push(new_buff);
|
||||
} else {
|
||||
let mut duplicate_existed = false;
|
||||
for i in 0..buffs.active_buffs.len() {
|
||||
let active_buff = &buffs.active_buffs[i].clone();
|
||||
// Checks if new buff has the same id as an already active buff. If it
|
||||
// doesn't, new buff added to active buffs. If it does, compares the new
|
||||
// buff and the active buff, and decides to either add new buff to
|
||||
// inactive buffs, or move active buff to
|
||||
// inactive buffs and add new buff to active
|
||||
// buffs.
|
||||
if discriminant(&active_buff.kind) == discriminant(&new_buff.kind) {
|
||||
duplicate_existed = true;
|
||||
// Determines if active buff is weaker than newer buff
|
||||
if determine_replace_active_buff(
|
||||
active_buff.clone(),
|
||||
new_buff.clone(),
|
||||
) {
|
||||
active_buff_indices_for_removal.push(i);
|
||||
add_buff_effects(new_buff.clone(), stats.get_mut(entity));
|
||||
buffs.active_buffs.push(new_buff.clone());
|
||||
// Sees if weaker active has longer duration than new buff
|
||||
#[allow(clippy::blocks_in_if_conditions)]
|
||||
if active_buff.time.map_or(true, |act_dur| {
|
||||
new_buff.time.map_or(false, |new_dur| act_dur > new_dur)
|
||||
}) {
|
||||
buffs.inactive_buffs.push(active_buff.clone());
|
||||
}
|
||||
// Sees if weaker new buff has longer duration
|
||||
// than active buff
|
||||
} else if let Some(active_dur) = active_buff.time {
|
||||
if let Some(new_dur) = new_buff.time {
|
||||
if new_dur > active_dur {
|
||||
buffs.inactive_buffs.push(new_buff.clone());
|
||||
}
|
||||
} else {
|
||||
buffs.inactive_buffs.push(new_buff.clone());
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if !duplicate_existed {
|
||||
add_buff_effects(new_buff.clone(), stats.get_mut(entity));
|
||||
buffs.active_buffs.push(new_buff);
|
||||
}
|
||||
}
|
||||
buffs.insert(new_buff);
|
||||
},
|
||||
BuffChange::RemoveExpiredByIndex(active_indices, inactive_indices) => {
|
||||
active_buff_indices_for_removal = active_indices;
|
||||
inactive_buff_indices_for_removal = inactive_indices;
|
||||
BuffChange::RemoveById(ids) => {
|
||||
for id in ids {
|
||||
buffs.remove(id);
|
||||
}
|
||||
},
|
||||
BuffChange::RemoveByKind(kind) => {
|
||||
for (i, buff) in buffs.active_buffs.iter().enumerate() {
|
||||
if discriminant(&kind) == discriminant(&buff.kind) {
|
||||
active_buff_indices_for_removal.push(i);
|
||||
}
|
||||
}
|
||||
for (i, buff) in buffs.inactive_buffs.iter().enumerate() {
|
||||
if discriminant(&kind) == discriminant(&buff.kind) {
|
||||
inactive_buff_indices_for_removal.push(i);
|
||||
}
|
||||
}
|
||||
buffs.remove_kind(kind);
|
||||
},
|
||||
BuffChange::RemoveFromClient(kind) => {
|
||||
for (i, buff) in buffs.active_buffs.iter().enumerate() {
|
||||
if discriminant(&kind) == discriminant(&buff.kind)
|
||||
&& buff
|
||||
.cat_ids
|
||||
.iter()
|
||||
.any(|cat| *cat == buff::BuffCategoryId::Buff)
|
||||
{
|
||||
active_buff_indices_for_removal.push(i);
|
||||
}
|
||||
}
|
||||
for (i, buff) in buffs.inactive_buffs.iter().enumerate() {
|
||||
if discriminant(&kind) == discriminant(&buff.kind)
|
||||
&& buff
|
||||
.cat_ids
|
||||
.iter()
|
||||
.any(|cat| *cat == buff::BuffCategoryId::Buff)
|
||||
{
|
||||
inactive_buff_indices_for_removal.push(i);
|
||||
}
|
||||
BuffChange::RemoveFromController(kind) => {
|
||||
if kind.is_buff() {
|
||||
buffs.remove_kind(kind);
|
||||
}
|
||||
},
|
||||
BuffChange::RemoveByCategory {
|
||||
required: all_required,
|
||||
optional: any_required,
|
||||
blacklisted: none_required,
|
||||
all_required,
|
||||
any_required,
|
||||
none_required,
|
||||
} => {
|
||||
for (i, buff) in buffs.active_buffs.iter().enumerate() {
|
||||
let mut ids_to_remove = Vec::new();
|
||||
for (id, buff) in buffs.buffs.iter() {
|
||||
let mut required_met = true;
|
||||
for required in &all_required {
|
||||
if !buff.cat_ids.iter().any(|cat| cat == required) {
|
||||
@ -814,7 +739,7 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange)
|
||||
}
|
||||
let mut any_met = any_required.is_empty();
|
||||
for any in &any_required {
|
||||
if !buff.cat_ids.iter().any(|cat| cat == any) {
|
||||
if buff.cat_ids.iter().any(|cat| cat == any) {
|
||||
any_met = true;
|
||||
break;
|
||||
}
|
||||
@ -827,126 +752,18 @@ pub fn handle_buff(server: &mut Server, uid: Uid, buff_change: buff::BuffChange)
|
||||
}
|
||||
}
|
||||
if required_met && any_met && none_met {
|
||||
active_buff_indices_for_removal.push(i);
|
||||
ids_to_remove.push(*id);
|
||||
}
|
||||
}
|
||||
for (i, buff) in buffs.inactive_buffs.iter().enumerate() {
|
||||
let mut required_met = true;
|
||||
for required in &all_required {
|
||||
if !buff.cat_ids.iter().any(|cat| cat == required) {
|
||||
required_met = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
let mut any_met = any_required.is_empty();
|
||||
for any in &any_required {
|
||||
if !buff.cat_ids.iter().any(|cat| cat == any) {
|
||||
any_met = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if required_met && any_met {
|
||||
inactive_buff_indices_for_removal.push(i);
|
||||
}
|
||||
for id in ids_to_remove {
|
||||
buffs.remove(id);
|
||||
}
|
||||
},
|
||||
}
|
||||
let mut removed_active_buff_kinds = Vec::new();
|
||||
while !active_buff_indices_for_removal.is_empty() {
|
||||
if let Some(i) = active_buff_indices_for_removal.pop() {
|
||||
let buff = buffs.active_buffs.remove(i);
|
||||
removed_active_buff_kinds.push(buff.kind);
|
||||
remove_buff_effects(buff, stats.get_mut(entity));
|
||||
}
|
||||
}
|
||||
while !inactive_buff_indices_for_removal.is_empty() {
|
||||
if let Some(i) = inactive_buff_indices_for_removal.pop() {
|
||||
buffs.inactive_buffs.remove(i);
|
||||
}
|
||||
}
|
||||
// Checks after buffs are removed so that it doesn't grab incorrect
|
||||
// index
|
||||
for buff_kind in removed_active_buff_kinds {
|
||||
// Checks to verify that there are no active buffs with the same id
|
||||
if buffs
|
||||
.active_buffs
|
||||
.iter()
|
||||
.any(|buff| discriminant(&buff.kind) == discriminant(&buff_kind))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let mut new_active_buff = None::<buff::Buff>;
|
||||
let mut replacement_buff_index = 0;
|
||||
for (i, inactive_buff) in buffs.inactive_buffs.iter().enumerate() {
|
||||
if discriminant(&buff_kind) == discriminant(&inactive_buff.kind) {
|
||||
if let Some(ref buff) = new_active_buff {
|
||||
if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) {
|
||||
new_active_buff = Some(inactive_buff.clone());
|
||||
replacement_buff_index = i;
|
||||
}
|
||||
} else {
|
||||
new_active_buff = Some(inactive_buff.clone());
|
||||
replacement_buff_index = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
if new_active_buff.is_some() {
|
||||
let buff = buffs.inactive_buffs.remove(replacement_buff_index);
|
||||
add_buff_effects(buff.clone(), stats.get_mut(entity));
|
||||
buffs.active_buffs.push(buff.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn determine_replace_active_buff(active_buff: buff::Buff, new_buff: buff::Buff) -> bool {
|
||||
use buff::BuffKind;
|
||||
match new_buff.kind {
|
||||
BuffKind::Bleeding {
|
||||
strength: new_strength,
|
||||
duration: new_duration,
|
||||
} => {
|
||||
if let BuffKind::Bleeding {
|
||||
strength: active_strength,
|
||||
duration: _,
|
||||
} = active_buff.kind
|
||||
{
|
||||
new_strength > active_strength
|
||||
|| (new_strength >= active_strength
|
||||
&& new_duration.map_or(true, |new_dur| {
|
||||
active_buff.time.map_or(false, |act_dur| new_dur > act_dur)
|
||||
}))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
BuffKind::Regeneration {
|
||||
strength: new_strength,
|
||||
duration: new_duration,
|
||||
} => {
|
||||
if let BuffKind::Regeneration {
|
||||
strength: active_strength,
|
||||
duration: _,
|
||||
} = active_buff.kind
|
||||
{
|
||||
new_strength > active_strength
|
||||
|| (new_strength >= active_strength
|
||||
&& new_duration.map_or(true, |new_dur| {
|
||||
active_buff.time.map_or(false, |act_dur| new_dur > act_dur)
|
||||
}))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
BuffKind::Cursed {
|
||||
duration: new_duration,
|
||||
} => new_duration.map_or(true, |new_dur| {
|
||||
active_buff.time.map_or(false, |act_dur| new_dur > act_dur)
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) {
|
||||
for effect in &buff.effects {
|
||||
use buff::BuffEffect;
|
||||
|
@ -74,6 +74,7 @@ treeculler = "0.1.0"
|
||||
uvth = "3.1.1"
|
||||
# vec_map = { version = "0.8.2" }
|
||||
const-tweaker = {version = "0.3.1", optional = true}
|
||||
inline_tweak = "1.0.2"
|
||||
itertools = "0.9.0"
|
||||
|
||||
# Logging
|
||||
|
@ -20,6 +20,7 @@ default = ["be-dyn-lib", "simd"]
|
||||
[dependencies]
|
||||
common = {package = "veloren-common", path = "../../../common"}
|
||||
find_folder = {version = "0.3.0", optional = true}
|
||||
inline_tweak = "1.0.2"
|
||||
lazy_static = {version = "1.4.0", optional = true}
|
||||
libloading = {version = "0.6.2", optional = true}
|
||||
notify = {version = "5.0.0-pre.2", optional = true}
|
||||
|
@ -90,8 +90,7 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::unused_unit)] // TODO: Pending review in #587
|
||||
fn style(&self) -> Self::Style { () }
|
||||
fn style(&self) -> Self::Style {}
|
||||
|
||||
fn update(self, args: widget::UpdateArgs<Self>) -> Self::Event {
|
||||
let widget::UpdateArgs { state, ui, .. } = args;
|
||||
@ -132,7 +131,7 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.set(state.ids.buffs_align, ui);
|
||||
|
||||
// Buffs and Debuffs
|
||||
let (buff_count, debuff_count) = buffs.active_buffs.iter().map(get_buff_info).fold(
|
||||
let (buff_count, debuff_count) = buffs.iter_active().map(get_buff_info).fold(
|
||||
(0, 0),
|
||||
|(buff_count, debuff_count), info| {
|
||||
if info.is_buff {
|
||||
@ -169,18 +168,13 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(
|
||||
buffs
|
||||
.active_buffs
|
||||
.iter()
|
||||
.iter_active()
|
||||
.map(get_buff_info)
|
||||
.filter(|info| info.is_buff),
|
||||
)
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), buff))| {
|
||||
let max_duration = match buff.kind {
|
||||
BuffKind::Bleeding { duration, .. } => duration,
|
||||
BuffKind::Regeneration { duration, .. } => duration,
|
||||
BuffKind::Cursed { duration } => duration,
|
||||
};
|
||||
let max_duration = buff.data.duration;
|
||||
let current_duration = buff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration
|
||||
@ -264,18 +258,13 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.zip(state.ids.debuff_timers.iter().copied())
|
||||
.zip(
|
||||
buffs
|
||||
.active_buffs
|
||||
.iter()
|
||||
.iter_active()
|
||||
.map(get_buff_info)
|
||||
.filter(|info| !info.is_buff),
|
||||
)
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), debuff))| {
|
||||
let max_duration = match debuff.kind {
|
||||
BuffKind::Bleeding { duration, .. } => duration,
|
||||
BuffKind::Regeneration { duration, .. } => duration,
|
||||
BuffKind::Cursed { duration } => duration,
|
||||
};
|
||||
let max_duration = debuff.data.duration;
|
||||
let current_duration = debuff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration
|
||||
@ -355,7 +344,7 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.set(state.ids.align, ui);
|
||||
|
||||
// Buffs and Debuffs
|
||||
let buff_count = buffs.active_buffs.len().min(11);
|
||||
let buff_count = buffs.kinds.len().min(11);
|
||||
// Limit displayed buffs
|
||||
let buff_count = buff_count.min(20);
|
||||
|
||||
@ -378,14 +367,10 @@ impl<'a> Widget for BuffsBar<'a> {
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(state.ids.buff_txts.iter().copied())
|
||||
.zip(buffs.active_buffs.iter().map(get_buff_info))
|
||||
.zip(buffs.iter_active().map(get_buff_info))
|
||||
.enumerate()
|
||||
.for_each(|(i, (((id, timer_id), txt_id), buff))| {
|
||||
let max_duration = match buff.kind {
|
||||
BuffKind::Bleeding { duration, .. } => duration,
|
||||
BuffKind::Regeneration { duration, .. } => duration,
|
||||
BuffKind::Cursed { duration } => duration,
|
||||
};
|
||||
let max_duration = buff.data.duration;
|
||||
let current_duration = buff.dur;
|
||||
// Percentage to determine which frame of the timer overlay is displayed
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
|
@ -444,7 +444,7 @@ impl<'a> Widget for Group<'a> {
|
||||
}
|
||||
if let Some(buffs) = buffs {
|
||||
// Limit displayed buffs to 11
|
||||
let buff_count = buffs.active_buffs.len().min(11);
|
||||
let buff_count = buffs.kinds.len().min(11);
|
||||
total_buff_count += buff_count;
|
||||
let gen = &mut ui.widget_id_generator();
|
||||
if state.ids.buffs.len() < total_buff_count {
|
||||
@ -464,13 +464,9 @@ impl<'a> Widget for Group<'a> {
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.skip(total_buff_count - buff_count)
|
||||
.zip(buffs.active_buffs.iter().map(get_buff_info))
|
||||
.zip(buffs.iter_active().map(get_buff_info))
|
||||
.for_each(|((id, timer_id), buff)| {
|
||||
let max_duration = match buff.kind {
|
||||
BuffKind::Bleeding { duration, .. } => duration,
|
||||
BuffKind::Regeneration { duration, .. } => duration,
|
||||
BuffKind::Cursed { duration } => duration,
|
||||
};
|
||||
let max_duration = buff.data.duration;
|
||||
let pulsating_col = Color::Rgba(1.0, 1.0, 1.0, buff_ani);
|
||||
let norm_col = Color::Rgba(1.0, 1.0, 1.0, 1.0);
|
||||
let current_duration = buff.dur;
|
||||
|
@ -275,6 +275,7 @@ widget_ids! {
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct BuffInfo {
|
||||
kind: comp::BuffKind,
|
||||
data: comp::BuffData,
|
||||
is_buff: bool,
|
||||
dur: Option<Duration>,
|
||||
}
|
||||
@ -2729,10 +2730,8 @@ pub fn get_quality_col<I: ItemDesc>(item: &I) -> Color {
|
||||
fn get_buff_info(buff: &comp::Buff) -> BuffInfo {
|
||||
BuffInfo {
|
||||
kind: buff.kind,
|
||||
is_buff: buff
|
||||
.cat_ids
|
||||
.iter()
|
||||
.any(|cat| *cat == comp::BuffCategoryId::Buff),
|
||||
data: buff.data,
|
||||
is_buff: buff.kind.is_buff(),
|
||||
dur: buff.time,
|
||||
}
|
||||
}
|
||||
|
@ -137,7 +137,7 @@ impl<'a> Ingameable for Overhead<'a> {
|
||||
self.info.map_or(0, |info| {
|
||||
2 + 1
|
||||
+ if self.bubble.is_none() {
|
||||
info.buffs.active_buffs.len().min(10) * 2
|
||||
info.buffs.kinds.len().min(10) * 2
|
||||
} else {
|
||||
0
|
||||
}
|
||||
@ -204,7 +204,7 @@ impl<'a> Widget for Overhead<'a> {
|
||||
};
|
||||
// Buffs
|
||||
// Alignment
|
||||
let buff_count = buffs.active_buffs.len().min(11);
|
||||
let buff_count = buffs.kinds.len().min(11);
|
||||
Rectangle::fill_with([168.0, 100.0], color::TRANSPARENT)
|
||||
.x_y(-1.0, name_y + 60.0)
|
||||
.parent(id)
|
||||
@ -229,15 +229,11 @@ impl<'a> Widget for Overhead<'a> {
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(state.ids.buff_timers.iter().copied())
|
||||
.zip(buffs.active_buffs.iter().map(get_buff_info))
|
||||
.zip(buffs.iter_active().map(get_buff_info))
|
||||
.enumerate()
|
||||
.for_each(|(i, ((id, timer_id), buff))| {
|
||||
// Limit displayed buffs
|
||||
let max_duration = match buff.kind {
|
||||
BuffKind::Bleeding { duration, .. } => duration,
|
||||
BuffKind::Regeneration { duration, .. } => duration,
|
||||
BuffKind::Cursed { duration } => duration,
|
||||
};
|
||||
let max_duration = buff.data.duration;
|
||||
let current_duration = buff.dur;
|
||||
let duration_percentage = current_duration.map_or(1000.0, |cur| {
|
||||
max_duration.map_or(1000.0, |max| {
|
||||
|
@ -1014,11 +1014,5 @@ impl<'a> Widget for Skillbar<'a> {
|
||||
.w_h(16.0, 18.0)
|
||||
.mid_bottom_with_margin_on(state.ids.m2_content, -11.0)
|
||||
.set(state.ids.m2_ico, ui);
|
||||
|
||||
// Buffs
|
||||
// Add debuff slots above the health bar
|
||||
// Add buff slots above the mana bar
|
||||
|
||||
// Debuffs
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user