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:
Sam 2020-10-24 15:12:37 -05:00
parent 337cf6e137
commit f60985d733
14 changed files with 257 additions and 408 deletions

11
Cargo.lock generated
View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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 },
)),
});

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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