mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Separated buffs into active and inactive buffs. There can only be 1 active buff at a time of a particular buff id. If a new buff is stronger than an active buff, it moves the active buff to inactive buffs. When active buffs are removed, it checks inactive buffs for any buffs of the same id and moves the strongest one to active buffs.
This commit is contained in:
parent
19c7ed7885
commit
ccad1fa0b8
@ -13,7 +13,7 @@ use std::time::Duration;
|
||||
/// making it harder to recognize which is which.
|
||||
///
|
||||
/// Also, this should be dehardcoded eventually.
|
||||
#[derive(Clone, PartialEq, Debug, Serialize, Deserialize)]
|
||||
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
||||
pub enum BuffId {
|
||||
/// Restores health/time for some period
|
||||
/// Has fields: strength (f32)
|
||||
@ -21,10 +21,9 @@ pub enum BuffId {
|
||||
/// Lowers health over time for some duration
|
||||
/// Has fields: strength (f32)
|
||||
Bleeding(f32),
|
||||
/// Adds a prefix to the entity name
|
||||
/// Prefixes an entity's name with "Cursed"
|
||||
/// Currently placeholder buff to show other stuff is possible
|
||||
/// Has fields: prefix (String)
|
||||
Prefix(String),
|
||||
Cursed,
|
||||
}
|
||||
|
||||
/// De/buff category ID.
|
||||
@ -85,7 +84,7 @@ pub enum BuffChange {
|
||||
/// Removes all buffs with this ID.
|
||||
RemoveById(BuffId),
|
||||
/// Removes buff of this index
|
||||
RemoveByIndex(Vec<usize>),
|
||||
RemoveByIndex(Vec<usize>, Vec<usize>),
|
||||
}
|
||||
|
||||
/// Source of the de/buff
|
||||
@ -124,13 +123,18 @@ pub enum BuffSource {
|
||||
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
||||
pub struct Buffs {
|
||||
/// Active de/buffs.
|
||||
pub buffs: Vec<Buff>,
|
||||
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>,
|
||||
}
|
||||
|
||||
impl Buffs {
|
||||
/// This is a primitive check if a specific buff is present.
|
||||
/// This is a primitive check if a specific buff is present and active.
|
||||
/// (for purposes like blocking usage of abilities or something like this).
|
||||
pub fn has_buff_id(&self, id: &BuffId) -> bool { self.buffs.iter().any(|buff| buff.id == *id) }
|
||||
pub fn has_buff_id(&self, id: &BuffId) -> bool {
|
||||
self.active_buffs.iter().any(|buff| buff.id == *id)
|
||||
}
|
||||
}
|
||||
|
||||
impl Buff {
|
||||
@ -150,16 +154,12 @@ impl Buff {
|
||||
rate: strength,
|
||||
accumulated: 0.0,
|
||||
}],
|
||||
BuffId::Prefix(ref prefix) => {
|
||||
let mut prefix = prefix.clone();
|
||||
prefix.push(' ');
|
||||
vec![BuffEffect::NameChange {
|
||||
prefix,
|
||||
}]
|
||||
},
|
||||
BuffId::Cursed => vec![BuffEffect::NameChange {
|
||||
prefix: String::from("Cursed "),
|
||||
}],
|
||||
};
|
||||
Buff {
|
||||
id: id.clone(),
|
||||
id,
|
||||
cat_ids,
|
||||
time,
|
||||
effects,
|
||||
|
@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
comp::{BuffChange, BuffEffect, BuffId, Buffs, HealthChange, HealthSource, Stats},
|
||||
comp::{BuffChange, BuffEffect, Buffs, HealthChange, HealthSource, Stats},
|
||||
event::{EventBus, ServerEvent},
|
||||
state::DeltaTime,
|
||||
sync::Uid,
|
||||
@ -28,13 +28,14 @@ impl<'a> System<'a> for Sys {
|
||||
let mut server_emitter = server_bus.emitter();
|
||||
for (entity, uid, mut buffs) in (&entities, &uids, &mut buffs.restrict_mut()).join() {
|
||||
let buff_comp = buffs.get_mut_unchecked();
|
||||
let mut buff_indices_for_removal = Vec::<usize>::new();
|
||||
let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) =
|
||||
(Vec::<usize>::new(), Vec::<usize>::new());
|
||||
// Tick all de/buffs on a Buffs component.
|
||||
for i in 0..buff_comp.buffs.len() {
|
||||
for i in 0..buff_comp.active_buffs.len() {
|
||||
// First, tick the buff and subtract delta from it
|
||||
// and return how much "real" time the buff took (for tick independence).
|
||||
// TODO: handle delta for "indefinite" buffs, i.e. time since they got removed.
|
||||
let buff_delta = if let Some(remaining_time) = &mut buff_comp.buffs[i].time {
|
||||
let buff_delta = if let Some(remaining_time) = &mut buff_comp.active_buffs[i].time {
|
||||
let pre_tick = remaining_time.as_secs_f32();
|
||||
let new_duration = remaining_time.checked_sub(Duration::from_secs_f32(dt.0));
|
||||
let post_tick = if let Some(dur) = new_duration {
|
||||
@ -44,7 +45,7 @@ impl<'a> System<'a> for Sys {
|
||||
} else {
|
||||
// The buff has expired.
|
||||
// Remove it.
|
||||
buff_indices_for_removal.push(i);
|
||||
active_buff_indices_for_removal.push(i);
|
||||
0.0
|
||||
};
|
||||
pre_tick - post_tick
|
||||
@ -56,7 +57,9 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
|
||||
// Now, execute the buff, based on it's delta
|
||||
for effect in &mut buff_comp.buffs[i].effects {
|
||||
for effect in &mut buff_comp.active_buffs[i].effects {
|
||||
#[allow(clippy::single_match)]
|
||||
// Remove clippy when more effects are added here
|
||||
match effect {
|
||||
// Only add an effect here if it is continuous or it is not immediate
|
||||
BuffEffect::HealthChangeOverTime { rate, accumulated } => {
|
||||
@ -77,9 +80,28 @@ impl<'a> System<'a> for Sys {
|
||||
};
|
||||
}
|
||||
}
|
||||
for i in 0..buff_comp.inactive_buffs.len() {
|
||||
// First, tick the buff and subtract delta from it
|
||||
// and return how much "real" time the buff took (for tick independence).
|
||||
// TODO: handle delta for "indefinite" buffs, i.e. time since they got removed.
|
||||
if let Some(remaining_time) = &mut buff_comp.inactive_buffs[i].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);
|
||||
} else {
|
||||
// The buff has expired.
|
||||
// Remove it.
|
||||
inactive_buff_indices_for_removal.push(i);
|
||||
};
|
||||
}
|
||||
}
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
uid: *uid,
|
||||
buff_change: BuffChange::RemoveByIndex(buff_indices_for_removal),
|
||||
buff_change: BuffChange::RemoveByIndex(
|
||||
active_buff_indices_for_removal,
|
||||
inactive_buff_indices_for_removal,
|
||||
),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -155,8 +155,8 @@ impl<'a> System<'a> for Sys {
|
||||
server_emitter.emit(ServerEvent::Buff {
|
||||
uid: *uid_b,
|
||||
buff_change: buff::BuffChange::Add(buff::Buff::new(
|
||||
buff::BuffId::Bleeding(50.0),
|
||||
Some(Duration::from_millis(5000)),
|
||||
buff::BuffId::Bleeding(-damage.healthchange),
|
||||
Some(Duration::from_millis(10000)),
|
||||
vec![buff::BuffCategoryId::Physical],
|
||||
)),
|
||||
});
|
||||
|
@ -24,6 +24,7 @@ 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;
|
||||
|
||||
@ -681,53 +682,158 @@ 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 buff_indices_for_removal = Vec::new();
|
||||
let (mut active_buff_indices_for_removal, mut inactive_buff_indices_for_removal) =
|
||||
(Vec::new(), Vec::new());
|
||||
match buff_change {
|
||||
buff::BuffChange::Add(new_buff) => {
|
||||
for effect in &new_buff.effects {
|
||||
match effect {
|
||||
// Only add an effect here if it is immediate and is not continuous
|
||||
buff::BuffEffect::NameChange { prefix } => {
|
||||
if let Some(stats) = stats.get_mut(entity) {
|
||||
let mut pref = String::from(prefix);
|
||||
pref.push_str(&stats.name);
|
||||
stats.name = pref;
|
||||
if buffs.active_buffs.is_empty() {
|
||||
add_buff_effects(new_buff.clone(), stats.get_mut(entity));
|
||||
buffs.active_buffs.push(new_buff);
|
||||
} else {
|
||||
for i in 0..buffs.active_buffs.len() {
|
||||
let active_buff = &buffs.active_buffs[i];
|
||||
// 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.id) == discriminant(&new_buff.id) {
|
||||
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());
|
||||
} else {
|
||||
buffs.inactive_buffs.push(new_buff.clone());
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
} else {
|
||||
add_buff_effects(new_buff.clone(), stats.get_mut(entity));
|
||||
buffs.active_buffs.push(new_buff.clone());
|
||||
}
|
||||
}
|
||||
}
|
||||
buffs.buffs.push(new_buff.clone());
|
||||
},
|
||||
buff::BuffChange::RemoveByIndex(indices) => {
|
||||
buff_indices_for_removal = indices;
|
||||
buff::BuffChange::RemoveByIndex(active_indices, inactive_indices) => {
|
||||
active_buff_indices_for_removal = active_indices;
|
||||
inactive_buff_indices_for_removal = inactive_indices;
|
||||
},
|
||||
buff::BuffChange::RemoveById(id) => {
|
||||
let some_predicate = |current_id: &buff::BuffId| *current_id == id;
|
||||
for i in 0..buffs.buffs.len() {
|
||||
if some_predicate(&mut buffs.buffs[i].id) {
|
||||
buff_indices_for_removal.push(i);
|
||||
for i in 0..buffs.active_buffs.len() {
|
||||
if some_predicate(&mut buffs.active_buffs[i].id) {
|
||||
active_buff_indices_for_removal.push(i);
|
||||
}
|
||||
}
|
||||
for i in 0..buffs.inactive_buffs.len() {
|
||||
if some_predicate(&mut buffs.inactive_buffs[i].id) {
|
||||
inactive_buff_indices_for_removal.push(i);
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
while !buff_indices_for_removal.is_empty() {
|
||||
if let Some(i) = buff_indices_for_removal.pop() {
|
||||
let buff = buffs.buffs.remove(i);
|
||||
for effect in &buff.effects {
|
||||
match effect {
|
||||
// Only remove an effect here if its effect was not continuously
|
||||
// applied
|
||||
buff::BuffEffect::NameChange { prefix } => {
|
||||
if let Some(stats) = stats.get_mut(entity) {
|
||||
stats.name = stats.name.replacen(prefix, "", 1);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
let mut removed_active_buff_ids = 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_ids.push(buff.id);
|
||||
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_id in removed_active_buff_ids {
|
||||
// Checks to verify that there are no active buffs with the same id
|
||||
if buffs
|
||||
.active_buffs
|
||||
.iter()
|
||||
.any(|buff| discriminant(&buff.id) == discriminant(&buff_id))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
let mut new_active_buff = None::<buff::Buff>;
|
||||
let mut replacement_buff_index = 0;
|
||||
for i in 0..buffs.inactive_buffs.len() {
|
||||
let inactive_buff = buffs.inactive_buffs[i].clone();
|
||||
if discriminant(&buff_id) == discriminant(&inactive_buff.id) {
|
||||
if let Some(ref buff) = new_active_buff {
|
||||
if determine_replace_active_buff(buff.clone(), inactive_buff.clone()) {
|
||||
new_active_buff = Some(inactive_buff);
|
||||
replacement_buff_index = i;
|
||||
}
|
||||
} else {
|
||||
new_active_buff = Some(inactive_buff);
|
||||
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::BuffId;
|
||||
match new_buff.id {
|
||||
BuffId::Bleeding(new_strength) => {
|
||||
if let BuffId::Bleeding(active_strength) = active_buff.id {
|
||||
new_strength > active_strength
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
BuffId::Regeneration(new_strength) => {
|
||||
if let BuffId::Regeneration(active_strength) = active_buff.id {
|
||||
new_strength > active_strength
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
BuffId::Cursed => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn add_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) {
|
||||
for effect in &buff.effects {
|
||||
#[allow(clippy::single_match)] // Remove clippy when there are more buff effects here
|
||||
match effect {
|
||||
// Only add an effect here if it is immediate and is not continuous
|
||||
buff::BuffEffect::NameChange { prefix } => {
|
||||
if let Some(ref mut stats) = stats {
|
||||
let mut pref = String::from(prefix);
|
||||
pref.push_str(&stats.name);
|
||||
stats.name = pref;
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn remove_buff_effects(buff: buff::Buff, mut stats: Option<&mut Stats>) {
|
||||
for effect in &buff.effects {
|
||||
#[allow(clippy::single_match)] // Remove clippy when there are more buff effects here
|
||||
match effect {
|
||||
// Only remove an effect here if its effect was not continuously
|
||||
// applied
|
||||
buff::BuffEffect::NameChange { prefix } => {
|
||||
if let Some(ref mut stats) = stats {
|
||||
stats.name = stats.name.replacen(prefix, "", 1);
|
||||
}
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user