2020-08-10 22:54:45 +00:00
|
|
|
use crate::sync::Uid;
|
|
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use specs::{Component, FlaggedStorage};
|
|
|
|
use specs_idvs::IdvStorage;
|
2020-10-25 01:20:03 +00:00
|
|
|
use std::{cmp::Ordering, collections::HashMap, time::Duration};
|
2020-08-10 22:54:45 +00:00
|
|
|
|
2020-10-19 03:00:35 +00:00
|
|
|
/// De/buff Kind.
|
2020-10-27 01:17:46 +00:00
|
|
|
/// This is used to determine what effects a buff will have
|
2020-10-24 20:12:37 +00:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
2020-10-19 03:00:35 +00:00
|
|
|
pub enum BuffKind {
|
2020-08-10 22:54:45 +00:00
|
|
|
/// Restores health/time for some period
|
2020-10-24 20:12:37 +00:00
|
|
|
Regeneration,
|
2020-10-27 21:27:19 +00:00
|
|
|
/// Restores health/time for some period for consumables
|
|
|
|
Saturation,
|
2020-10-01 00:40:46 +00:00
|
|
|
/// Lowers health over time for some duration
|
2020-10-24 20:12:37 +00:00
|
|
|
Bleeding,
|
2020-10-27 01:17:46 +00:00
|
|
|
/// Lower a creature's max health
|
2020-10-01 17:33:35 +00:00
|
|
|
/// Currently placeholder buff to show other stuff is possible
|
2020-10-24 20:12:37 +00:00
|
|
|
Cursed,
|
2020-11-05 20:02:54 +00:00
|
|
|
// Applied when drinking a potion
|
|
|
|
Potion,
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl BuffKind {
|
|
|
|
// Checks if buff is buff or debuff
|
|
|
|
pub fn is_buff(self) -> bool {
|
|
|
|
match self {
|
|
|
|
BuffKind::Regeneration { .. } => true,
|
2020-10-27 21:27:19 +00:00
|
|
|
BuffKind::Saturation { .. } => true,
|
2020-10-24 20:12:37 +00:00
|
|
|
BuffKind::Bleeding { .. } => false,
|
|
|
|
BuffKind::Cursed { .. } => false,
|
2020-11-05 20:56:45 +00:00
|
|
|
BuffKind::Potion { .. } => true,
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Struct used to store data relevant to a buff
|
2020-10-27 21:27:19 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
|
2020-10-24 20:12:37 +00:00
|
|
|
pub struct BuffData {
|
|
|
|
pub strength: f32,
|
|
|
|
pub duration: Option<Duration>,
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// De/buff category ID.
|
2020-10-19 03:00:35 +00:00
|
|
|
/// Similar to `BuffKind`, but to mark a category (for more generic usage, like
|
2020-08-10 22:54:45 +00:00
|
|
|
/// positive/negative buffs).
|
2020-10-02 19:09:19 +00:00
|
|
|
#[derive(Clone, Copy, Eq, PartialEq, Debug, Serialize, Deserialize)]
|
2020-10-24 20:12:37 +00:00
|
|
|
pub enum BuffCategory {
|
2020-08-10 22:54:45 +00:00
|
|
|
Natural,
|
2020-10-01 00:40:46 +00:00
|
|
|
Physical,
|
2020-08-10 22:54:45 +00:00
|
|
|
Magical,
|
|
|
|
Divine,
|
2020-10-13 00:48:25 +00:00
|
|
|
PersistOnDeath,
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 23:07:38 +00:00
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
|
|
pub enum ModifierKind {
|
|
|
|
Additive,
|
|
|
|
Multiplicative,
|
|
|
|
}
|
|
|
|
|
2020-08-10 22:54:45 +00:00
|
|
|
/// Data indicating and configuring behaviour of a de/buff.
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
2020-10-01 00:40:46 +00:00
|
|
|
pub enum BuffEffect {
|
|
|
|
/// Periodically damages or heals entity
|
2020-10-01 01:35:57 +00:00
|
|
|
HealthChangeOverTime { rate: f32, accumulated: f32 },
|
2020-10-24 23:07:38 +00:00
|
|
|
/// Changes maximum health by a certain amount
|
|
|
|
MaxHealthModifier { value: f32, kind: ModifierKind },
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Actual de/buff.
|
|
|
|
/// Buff can timeout after some time if `time` is Some. If `time` is None,
|
|
|
|
/// Buff will last indefinitely, until removed manually (by some action, like
|
2020-10-19 03:00:35 +00:00
|
|
|
/// uncursing).
|
2020-08-10 22:54:45 +00:00
|
|
|
///
|
2020-10-19 03:00:35 +00:00
|
|
|
/// Buff has a kind, which is used to determine the effects in a builder
|
|
|
|
/// function.
|
2020-08-10 22:54:45 +00:00
|
|
|
///
|
|
|
|
/// To provide more classification info when needed,
|
|
|
|
/// buff can be in one or more buff category.
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
|
|
pub struct Buff {
|
2020-10-19 03:00:35 +00:00
|
|
|
pub kind: BuffKind,
|
2020-10-24 20:12:37 +00:00
|
|
|
pub data: BuffData,
|
|
|
|
pub cat_ids: Vec<BuffCategory>,
|
2020-08-10 22:54:45 +00:00
|
|
|
pub time: Option<Duration>,
|
2020-10-01 00:40:46 +00:00
|
|
|
pub effects: Vec<BuffEffect>,
|
2020-10-03 18:48:56 +00:00
|
|
|
pub source: BuffSource,
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Information about whether buff addition or removal was requested.
|
|
|
|
/// This to implement "on_add" and "on_remove" hooks for constant buffs.
|
2020-10-24 20:12:37 +00:00
|
|
|
#[derive(Clone, Debug)]
|
2020-08-10 22:54:45 +00:00
|
|
|
pub enum BuffChange {
|
|
|
|
/// Adds this buff.
|
|
|
|
Add(Buff),
|
|
|
|
/// Removes all buffs with this ID.
|
2020-10-19 03:00:35 +00:00
|
|
|
RemoveByKind(BuffKind),
|
|
|
|
/// Removes all buffs with this ID, but not debuffs.
|
2020-10-24 20:12:37 +00:00
|
|
|
RemoveFromController(BuffKind),
|
2020-10-02 19:09:19 +00:00
|
|
|
/// Removes buffs of these indices (first vec is for active buffs, second is
|
2020-10-19 03:00:35 +00:00
|
|
|
/// for inactive buffs), should only be called when buffs expire
|
2020-10-24 20:12:37 +00:00
|
|
|
RemoveById(Vec<BuffId>),
|
2020-10-02 19:09:19 +00:00
|
|
|
/// 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
|
2020-10-27 21:27:19 +00:00
|
|
|
/// required, third vec is of categories that will not be removed)
|
2020-10-13 00:48:25 +00:00
|
|
|
RemoveByCategory {
|
2020-10-24 20:12:37 +00:00
|
|
|
all_required: Vec<BuffCategory>,
|
|
|
|
any_required: Vec<BuffCategory>,
|
|
|
|
none_required: Vec<BuffCategory>,
|
2020-10-13 00:48:25 +00:00
|
|
|
},
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 20:12:37 +00:00
|
|
|
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,
|
|
|
|
),
|
2020-11-05 20:02:54 +00:00
|
|
|
BuffKind::Regeneration | BuffKind::Saturation | BuffKind::Potion => (
|
2020-10-24 20:12:37 +00:00
|
|
|
vec![BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: data.strength,
|
|
|
|
accumulated: 0.0,
|
|
|
|
}],
|
|
|
|
data.duration,
|
2020-11-05 20:56:45 +00:00
|
|
|
),
|
2020-10-24 20:12:37 +00:00
|
|
|
BuffKind::Cursed => (
|
2020-10-24 23:07:38 +00:00
|
|
|
vec![BuffEffect::MaxHealthModifier {
|
2020-10-25 01:20:03 +00:00
|
|
|
value: -100. * data.strength,
|
2020-10-24 23:07:38 +00:00
|
|
|
kind: ModifierKind::Additive,
|
2020-10-24 20:12:37 +00:00
|
|
|
}],
|
|
|
|
data.duration,
|
|
|
|
),
|
|
|
|
};
|
|
|
|
Buff {
|
|
|
|
kind,
|
|
|
|
data,
|
|
|
|
cat_ids,
|
|
|
|
time,
|
|
|
|
effects,
|
|
|
|
source,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-25 01:20:03 +00:00
|
|
|
impl PartialOrd for Buff {
|
|
|
|
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
2020-10-27 01:17:46 +00:00
|
|
|
if self == other {
|
|
|
|
Some(Ordering::Equal)
|
|
|
|
} else if self.data.strength > other.data.strength {
|
2020-10-25 01:20:03 +00:00
|
|
|
Some(Ordering::Greater)
|
|
|
|
} else if self.data.strength < other.data.strength {
|
|
|
|
Some(Ordering::Less)
|
|
|
|
} else if compare_duration(self.time, other.time) {
|
|
|
|
Some(Ordering::Greater)
|
|
|
|
} else if compare_duration(other.time, self.time) {
|
|
|
|
Some(Ordering::Less)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn compare_duration(a: Option<Duration>, b: Option<Duration>) -> bool {
|
|
|
|
a.map_or(true, |dur_a| b.map_or(false, |dur_b| dur_a > dur_b))
|
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Buff {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
2020-10-27 01:17:46 +00:00
|
|
|
self.data.strength == other.data.strength && self.time == other.time
|
2020-10-25 01:20:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-10 22:54:45 +00:00
|
|
|
/// Source of the de/buff
|
|
|
|
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
|
|
|
|
pub enum BuffSource {
|
|
|
|
/// Applied by a character
|
|
|
|
Character { by: Uid },
|
|
|
|
/// Applied by world, like a poisonous fumes from a swamp
|
|
|
|
World,
|
|
|
|
/// Applied by command
|
|
|
|
Command,
|
|
|
|
/// Applied by an item
|
|
|
|
Item,
|
|
|
|
/// Applied by another buff (like an after-effect)
|
|
|
|
Buff,
|
|
|
|
/// Some other source
|
|
|
|
Unknown,
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Component holding all de/buffs that gets resolved each tick.
|
|
|
|
/// On each tick, remaining time of buffs get lowered and
|
2020-10-01 00:40:46 +00:00
|
|
|
/// buff effect of each buff is applied or not, depending on the `BuffEffect`
|
2020-10-01 01:35:57 +00:00
|
|
|
/// (specs system will decide based on `BuffEffect`, to simplify
|
|
|
|
/// implementation). TODO: Something like `once` flag for `Buff` to remove the
|
|
|
|
/// dependence on `BuffEffect` enum?
|
2020-08-10 22:54:45 +00:00
|
|
|
///
|
|
|
|
/// In case of one-time buffs, buff effects will be applied on addition
|
|
|
|
/// and undone on removal of the buff (by the specs system).
|
|
|
|
/// Example could be decreasing max health, which, if repeated each tick,
|
|
|
|
/// would be probably an undesired effect).
|
|
|
|
#[derive(Clone, Debug, Serialize, Deserialize, Default)]
|
|
|
|
pub struct Buffs {
|
2020-10-24 20:17:49 +00:00
|
|
|
/// Uid used for synchronization
|
2020-10-24 20:12:37 +00:00
|
|
|
id_counter: u64,
|
2020-10-24 20:17:49 +00:00
|
|
|
/// Maps Kinds of buff to Id's of currently applied buffs of that kind
|
2020-10-24 20:12:37 +00:00
|
|
|
pub kinds: HashMap<BuffKind, Vec<BuffId>>,
|
2020-10-24 20:17:49 +00:00
|
|
|
// All currently applied buffs stored by Id
|
2020-10-24 20:12:37 +00:00
|
|
|
pub buffs: HashMap<BuffId, Buff>,
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
|
|
|
|
2020-10-24 20:12:37 +00:00
|
|
|
impl Buffs {
|
|
|
|
fn sort_kind(&mut self, kind: BuffKind) {
|
|
|
|
if let Some(buff_order) = self.kinds.get_mut(&kind) {
|
2020-10-25 01:20:03 +00:00
|
|
|
if buff_order.is_empty() {
|
2020-10-24 20:12:37 +00:00
|
|
|
self.kinds.remove(&kind);
|
|
|
|
} else {
|
|
|
|
let buffs = &self.buffs;
|
2020-10-25 01:20:03 +00:00
|
|
|
// Intentionally sorted in reverse so that the strongest buffs are earlier in
|
|
|
|
// the vector
|
|
|
|
buff_order.sort_by(|a, b| buffs[&b].partial_cmp(&buffs[&a]).unwrap());
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
2020-10-01 17:33:35 +00:00
|
|
|
}
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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())
|
2020-10-25 01:20:03 +00:00
|
|
|
.unwrap_or_else(|| (&[]).iter())
|
2020-10-24 20:12:37 +00:00
|
|
|
.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);
|
2020-10-01 17:33:35 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-24 20:12:37 +00:00
|
|
|
pub type BuffId = u64;
|
|
|
|
|
2020-08-10 22:54:45 +00:00
|
|
|
impl Component for Buffs {
|
|
|
|
type Storage = FlaggedStorage<Self, IdvStorage<Self>>;
|
|
|
|
}
|