2021-07-11 18:41:52 +00:00
|
|
|
#![allow(clippy::nonstandard_macro_braces)] //tmp as of false positive !?
|
2023-03-10 01:27:54 +00:00
|
|
|
use crate::{
|
2023-05-19 03:07:44 +00:00
|
|
|
combat::{AttackEffect, CombatBuff, CombatBuffStrength, CombatEffect},
|
2022-12-16 02:51:03 +00:00
|
|
|
comp::{aura::AuraKey, Health, Stats},
|
2023-03-11 21:44:57 +00:00
|
|
|
resources::{Secs, Time},
|
2023-03-10 01:27:54 +00:00
|
|
|
uid::Uid,
|
|
|
|
};
|
2023-05-19 03:07:44 +00:00
|
|
|
|
2023-03-08 03:22:54 +00:00
|
|
|
use core::cmp::Ordering;
|
2020-12-12 01:45:46 +00:00
|
|
|
use hashbrown::HashMap;
|
2023-01-14 20:37:31 +00:00
|
|
|
use itertools::Either;
|
2020-08-10 22:54:45 +00:00
|
|
|
use serde::{Deserialize, Serialize};
|
2022-08-08 04:38:20 +00:00
|
|
|
use specs::{Component, DerefFlaggedStorage, VecStorage};
|
2022-04-04 14:03:16 +00:00
|
|
|
use strum::EnumIter;
|
2020-08-10 22:54:45 +00:00
|
|
|
|
2023-03-29 23:11:59 +00:00
|
|
|
use super::Body;
|
|
|
|
|
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
|
2021-05-04 13:22:10 +00:00
|
|
|
#[derive(
|
|
|
|
Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, PartialOrd, Ord, EnumIter,
|
|
|
|
)]
|
2020-10-19 03:00:35 +00:00
|
|
|
pub enum BuffKind {
|
2021-05-03 20:29:08 +00:00
|
|
|
// Buffs
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Restores health/time for some period.
|
|
|
|
/// Strength should be the healing per second.
|
2020-10-24 20:12:37 +00:00
|
|
|
Regeneration,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Restores health/time for some period for consumables.
|
|
|
|
/// Strength should be the healing per second.
|
2020-10-27 21:27:19 +00:00
|
|
|
Saturation,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Applied when drinking a potion.
|
|
|
|
/// Strength should be the healing per second.
|
2020-11-05 20:02:54 +00:00
|
|
|
Potion,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Applied when sitting at a campfire.
|
|
|
|
/// Strength is fraction of health restored per second.
|
2020-12-04 22:24:56 +00:00
|
|
|
CampfireHeal,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Restores energy/time for some period.
|
|
|
|
/// Strength should be the healing per second.
|
2022-09-05 14:16:18 +00:00
|
|
|
EnergyRegen,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Raises maximum energy.
|
|
|
|
/// Strength should be 10x the effect to max energy.
|
StaminaPlus buff, modifying stamina via buffs
trying to fix this, coming back to this later
please remember to change potion back future self!
this ALMOST works. maybe MR ready, kinda jank tho
so close, and yet so far...
IT WORKS IT WORKS IT WORKS IT WORKS IT WORKS IT WO
did the same with health, ill fix this garbage l8r
think we're basically done here
whoops forgot to change the food back
fixing and cleaning up part 1
fixed everything part 2 now with buff images
ran clippy + fmt, fixed items that i modified
bracket bulldozing, boldly
hopefully this should be good?
need to rebase real quick
please let me be done
StaminaPlus buff, modifying stamina via buffs
trying to fix this, coming back to this later
please remember to change potion back future self!
this ALMOST works. maybe MR ready, kinda jank tho
so close, and yet so far...
IT WORKS IT WORKS IT WORKS IT WORKS IT WORKS IT WO
did the same with health, ill fix this garbage l8r
think we're basically done here
whoops forgot to change the food back
fixing and cleaning up part 1
fixed everything part 2 now with buff images
ran clippy + fmt, fixed items that i modified
hopefully this should be good?
cargo clippy fmt stuff
deleted an extraneous file?? how did that even...?
2021-01-26 22:47:55 +00:00
|
|
|
IncreaseMaxEnergy,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Raises maximum health.
|
|
|
|
/// Strength should be the effect to max health.
|
StaminaPlus buff, modifying stamina via buffs
trying to fix this, coming back to this later
please remember to change potion back future self!
this ALMOST works. maybe MR ready, kinda jank tho
so close, and yet so far...
IT WORKS IT WORKS IT WORKS IT WORKS IT WORKS IT WO
did the same with health, ill fix this garbage l8r
think we're basically done here
whoops forgot to change the food back
fixing and cleaning up part 1
fixed everything part 2 now with buff images
ran clippy + fmt, fixed items that i modified
bracket bulldozing, boldly
hopefully this should be good?
need to rebase real quick
please let me be done
StaminaPlus buff, modifying stamina via buffs
trying to fix this, coming back to this later
please remember to change potion back future self!
this ALMOST works. maybe MR ready, kinda jank tho
so close, and yet so far...
IT WORKS IT WORKS IT WORKS IT WORKS IT WORKS IT WO
did the same with health, ill fix this garbage l8r
think we're basically done here
whoops forgot to change the food back
fixing and cleaning up part 1
fixed everything part 2 now with buff images
ran clippy + fmt, fixed items that i modified
hopefully this should be good?
cargo clippy fmt stuff
deleted an extraneous file?? how did that even...?
2021-01-26 22:47:55 +00:00
|
|
|
IncreaseMaxHealth,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Makes you immune to attacks.
|
|
|
|
/// Strength does not affect this buff.
|
2021-02-28 20:02:03 +00:00
|
|
|
Invulnerability,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Reduces incoming damage.
|
2021-04-24 19:01:36 +00:00
|
|
|
/// Strength scales the damage reduction non-linearly. 0.5 provides 50% DR,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// 1.0 provides 67% DR.
|
2021-03-04 03:43:11 +00:00
|
|
|
ProtectingWard,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Increases movement speed and gives health regeneration.
|
2021-04-24 19:01:36 +00:00
|
|
|
/// Strength scales the movement speed linearly. 0.5 is 150% speed, 1.0 is
|
2023-04-07 06:10:24 +00:00
|
|
|
/// 200% speed. Provides regeneration at 10x the value of the strength.
|
2021-04-24 19:01:36 +00:00
|
|
|
Frenzied,
|
2023-02-11 00:31:07 +00:00
|
|
|
/// Increases movement and attack speed, but removes chance to get critical
|
|
|
|
/// hits. Strength scales strength of both effects linearly. 0.5 is a
|
|
|
|
/// 50% increase, 1.0 is a 100% increase.
|
2022-02-09 01:23:23 +00:00
|
|
|
Hastened,
|
2022-12-16 02:51:03 +00:00
|
|
|
/// Increases resistance to incoming poise, and poise damage dealt as health
|
2023-04-07 06:10:24 +00:00
|
|
|
/// is lost from the time the buff activated.
|
2022-12-16 02:51:03 +00:00
|
|
|
/// Strength scales the resistance non-linearly. 0.5 provides 50%, 1.0
|
2023-04-07 06:10:24 +00:00
|
|
|
/// provides 67%.
|
2022-12-16 02:51:03 +00:00
|
|
|
/// Strength scales the poise damage increase linearly, a strength of 1.0
|
|
|
|
/// and n health less from activation will cause poise damage to increase by
|
2023-04-07 06:10:24 +00:00
|
|
|
/// n%.
|
2022-03-05 20:51:41 +00:00
|
|
|
Fortitude,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Increases both attack damage and vulnerability to damage.
|
|
|
|
/// Damage increases linearly with strength, 1.0 is a 100% increase.
|
2022-12-19 18:26:03 +00:00
|
|
|
/// Damage reduction decreases linearly with strength, 1.0 is a 100%
|
2023-04-07 06:10:24 +00:00
|
|
|
/// decrease.
|
2022-12-19 18:26:03 +00:00
|
|
|
Reckless,
|
2021-05-03 20:29:08 +00:00
|
|
|
// Debuffs
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Does damage to a creature over time.
|
|
|
|
/// Strength should be the DPS of the debuff.
|
2021-05-03 20:29:08 +00:00
|
|
|
Burning,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Lowers health over time for some duration.
|
|
|
|
/// Strength should be the DPS of the debuff.
|
2021-05-03 20:29:08 +00:00
|
|
|
Bleeding,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Lower a creature's max health over time.
|
2021-05-03 20:29:08 +00:00
|
|
|
/// Strength only affects the target max health, 0.5 targets 50% of base
|
2023-04-07 06:10:24 +00:00
|
|
|
/// max, 1.0 targets 100% of base max.
|
2021-05-03 20:29:08 +00:00
|
|
|
Cursed,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Reduces movement speed and causes bleeding damage.
|
2021-05-03 20:29:08 +00:00
|
|
|
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
|
2021-09-09 04:07:17 +00:00
|
|
|
/// speed, 1.0 is 33% speed. Bleeding is at 4x the value of the strength.
|
2021-05-03 20:29:08 +00:00
|
|
|
Crippled,
|
2021-05-30 20:39:18 +00:00
|
|
|
/// Slows movement and attack speed.
|
|
|
|
/// Strength scales the attack speed debuff non-linearly. 0.5 is ~50%
|
|
|
|
/// speed, 1.0 is 33% speed. Movement speed debuff is scaled to be slightly
|
2021-05-30 20:42:47 +00:00
|
|
|
/// smaller than attack speed debuff.
|
2021-05-30 15:51:47 +00:00
|
|
|
Frozen,
|
2021-05-24 00:45:22 +00:00
|
|
|
/// Makes you wet and causes you to have reduced friction on the ground.
|
|
|
|
/// Strength scales the friction you ignore non-linearly. 0.5 is 50% ground
|
|
|
|
/// friction, 1.0 is 33% ground friction.
|
|
|
|
Wet,
|
2021-06-20 05:37:22 +00:00
|
|
|
/// Makes you move slower.
|
|
|
|
/// Strength scales the movement speed debuff non-linearly. 0.5 is 50%
|
|
|
|
/// speed, 1.0 is 33% speed.
|
|
|
|
Ensnared,
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Drain stamina to a creature over time.
|
|
|
|
/// Strength should be the energy per second of the debuff.
|
2021-09-02 23:57:17 +00:00
|
|
|
Poisoned,
|
2022-09-07 01:23:12 +00:00
|
|
|
/// Results from having an attack parried.
|
|
|
|
/// Causes your attack speed to be slower to emulate the recover duration of
|
|
|
|
/// an ability being lengthened.
|
|
|
|
Parried,
|
2023-01-14 00:48:59 +00:00
|
|
|
/// Results from drinking a potion.
|
|
|
|
/// Decreases the health gained from subsequent potions.
|
|
|
|
PotionSickness,
|
2023-03-29 23:11:59 +00:00
|
|
|
// Changed into another body.
|
|
|
|
Polymorphed(Body),
|
2023-05-19 03:07:44 +00:00
|
|
|
// Inflict burning on your attack
|
|
|
|
Flame,
|
|
|
|
// Inflict frost on your attack
|
|
|
|
Frigid,
|
|
|
|
// Gain Lifesteal on your attack
|
|
|
|
Lifesteal,
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl BuffKind {
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Checks if buff is buff or debuff.
|
2020-10-24 20:12:37 +00:00
|
|
|
pub fn is_buff(self) -> bool {
|
|
|
|
match self {
|
2021-07-28 10:38:53 +00:00
|
|
|
BuffKind::Regeneration
|
|
|
|
| BuffKind::Saturation
|
|
|
|
| BuffKind::Potion
|
|
|
|
| BuffKind::CampfireHeal
|
2021-08-08 16:37:52 +00:00
|
|
|
| BuffKind::Frenzied
|
2022-09-05 14:16:18 +00:00
|
|
|
| BuffKind::EnergyRegen
|
2021-07-28 10:38:53 +00:00
|
|
|
| BuffKind::IncreaseMaxEnergy
|
|
|
|
| BuffKind::IncreaseMaxHealth
|
|
|
|
| BuffKind::Invulnerability
|
2022-02-09 01:23:23 +00:00
|
|
|
| BuffKind::ProtectingWard
|
2022-03-05 20:51:41 +00:00
|
|
|
| BuffKind::Hastened
|
2022-12-19 18:26:03 +00:00
|
|
|
| BuffKind::Fortitude
|
2023-05-19 03:07:44 +00:00
|
|
|
| BuffKind::Reckless
|
|
|
|
| BuffKind::Flame
|
|
|
|
| BuffKind::Frigid
|
|
|
|
| BuffKind::Lifesteal => true,
|
2021-07-28 10:38:53 +00:00
|
|
|
BuffKind::Bleeding
|
|
|
|
| BuffKind::Cursed
|
|
|
|
| BuffKind::Burning
|
|
|
|
| BuffKind::Crippled
|
|
|
|
| BuffKind::Frozen
|
|
|
|
| BuffKind::Wet
|
2021-09-02 23:57:17 +00:00
|
|
|
| BuffKind::Ensnared
|
2022-09-07 01:23:12 +00:00
|
|
|
| BuffKind::Poisoned
|
2023-01-14 00:48:59 +00:00
|
|
|
| BuffKind::Parried
|
2023-03-29 23:11:59 +00:00
|
|
|
| BuffKind::PotionSickness
|
|
|
|
| BuffKind::Polymorphed(_) => false,
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-20 20:37:46 +00:00
|
|
|
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Checks if buff should queue.
|
2021-02-20 20:37:46 +00:00
|
|
|
pub fn queues(self) -> bool { matches!(self, BuffKind::Saturation) }
|
2023-01-14 00:48:59 +00:00
|
|
|
|
|
|
|
/// 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) }
|
2023-03-29 23:11:59 +00:00
|
|
|
|
|
|
|
pub fn effects(
|
|
|
|
&self,
|
|
|
|
data: &BuffData,
|
|
|
|
stats: Option<&Stats>,
|
|
|
|
health: Option<&Health>,
|
|
|
|
) -> Vec<BuffEffect> {
|
|
|
|
// Normalized nonlinear scaling
|
|
|
|
let nn_scaling = |a| a / (a + 0.5);
|
|
|
|
let instance = rand::random();
|
|
|
|
match self {
|
|
|
|
BuffKind::Bleeding => vec![BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: -data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(0.5),
|
|
|
|
}],
|
|
|
|
BuffKind::Regeneration => vec![BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
|
|
|
tick_dur: Secs(1.0),
|
|
|
|
}],
|
|
|
|
BuffKind::Saturation => vec![BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
|
|
|
tick_dur: Secs(3.0),
|
2023-03-29 23:11:59 +00:00
|
|
|
}],
|
|
|
|
BuffKind::Potion => {
|
|
|
|
vec![BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: data.strength * stats.map_or(1.0, |s| s.heal_multiplier),
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(0.1),
|
2023-03-29 23:11:59 +00:00
|
|
|
}]
|
|
|
|
},
|
|
|
|
BuffKind::CampfireHeal => vec![BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: data.strength,
|
|
|
|
kind: ModifierKind::Fractional,
|
|
|
|
instance,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(2.0),
|
2023-03-29 23:11:59 +00:00
|
|
|
}],
|
|
|
|
BuffKind::Cursed => vec![
|
|
|
|
BuffEffect::MaxHealthChangeOverTime {
|
|
|
|
rate: -1.0,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
target_fraction: 1.0 - data.strength,
|
|
|
|
},
|
|
|
|
BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: -1.0,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(0.5),
|
2023-03-29 23:11:59 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
BuffKind::EnergyRegen => vec![BuffEffect::EnergyChangeOverTime {
|
|
|
|
rate: data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(1.0),
|
2023-03-29 23:11:59 +00:00
|
|
|
}],
|
|
|
|
BuffKind::IncreaseMaxEnergy => vec![BuffEffect::MaxEnergyModifier {
|
|
|
|
value: data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
}],
|
|
|
|
BuffKind::IncreaseMaxHealth => vec![BuffEffect::MaxHealthModifier {
|
|
|
|
value: data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
}],
|
|
|
|
BuffKind::Invulnerability => vec![BuffEffect::DamageReduction(1.0)],
|
|
|
|
BuffKind::ProtectingWard => vec![BuffEffect::DamageReduction(
|
|
|
|
// Causes non-linearity in effect strength, but necessary
|
|
|
|
// to allow for tool power and other things to affect the
|
|
|
|
// strength. 0.5 also still provides 50% damage reduction.
|
|
|
|
nn_scaling(data.strength),
|
|
|
|
)],
|
|
|
|
BuffKind::Burning => vec![BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: -data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(0.25),
|
2023-03-29 23:11:59 +00:00
|
|
|
}],
|
|
|
|
BuffKind::Poisoned => vec![BuffEffect::EnergyChangeOverTime {
|
|
|
|
rate: -data.strength,
|
|
|
|
kind: ModifierKind::Additive,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(0.5),
|
2023-03-29 23:11:59 +00:00
|
|
|
}],
|
|
|
|
BuffKind::Crippled => vec![
|
|
|
|
BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength)),
|
|
|
|
BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: -data.strength * 4.0,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(0.5),
|
2023-03-29 23:11:59 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
BuffKind::Frenzied => vec![
|
|
|
|
BuffEffect::MovementSpeed(1.0 + data.strength),
|
|
|
|
BuffEffect::HealthChangeOverTime {
|
|
|
|
rate: data.strength * 10.0,
|
|
|
|
kind: ModifierKind::Additive,
|
|
|
|
instance,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs(1.0),
|
2023-03-29 23:11:59 +00:00
|
|
|
},
|
|
|
|
],
|
|
|
|
BuffKind::Frozen => vec![
|
|
|
|
BuffEffect::MovementSpeed(f32::powf(1.0 - nn_scaling(data.strength), 1.1)),
|
|
|
|
BuffEffect::AttackSpeed(1.0 - nn_scaling(data.strength)),
|
|
|
|
],
|
|
|
|
BuffKind::Wet => vec![BuffEffect::GroundFriction(1.0 - nn_scaling(data.strength))],
|
|
|
|
BuffKind::Ensnared => vec![BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength))],
|
|
|
|
BuffKind::Hastened => vec![
|
|
|
|
BuffEffect::MovementSpeed(1.0 + data.strength),
|
|
|
|
BuffEffect::AttackSpeed(1.0 + data.strength),
|
|
|
|
BuffEffect::CriticalChance(0.0),
|
|
|
|
],
|
|
|
|
BuffKind::Fortitude => vec![
|
|
|
|
BuffEffect::PoiseReduction(nn_scaling(data.strength)),
|
|
|
|
BuffEffect::PoiseDamageFromLostHealth {
|
|
|
|
initial_health: health.map_or(0.0, |h| h.current()),
|
|
|
|
strength: data.strength,
|
|
|
|
},
|
|
|
|
],
|
|
|
|
BuffKind::Parried => vec![BuffEffect::AttackSpeed(0.5)],
|
|
|
|
BuffKind::PotionSickness => vec![BuffEffect::HealReduction(data.strength)],
|
|
|
|
BuffKind::Reckless => vec![
|
|
|
|
BuffEffect::DamageReduction(-data.strength),
|
|
|
|
BuffEffect::AttackDamage(1.0 + data.strength),
|
|
|
|
],
|
|
|
|
BuffKind::Polymorphed(body) => vec![BuffEffect::BodyChange(*body)],
|
2023-05-19 03:07:44 +00:00
|
|
|
BuffKind::Flame => vec![BuffEffect::BuffOnHit(AttackEffect::new(
|
|
|
|
None,
|
|
|
|
CombatEffect::Buff(CombatBuff {
|
|
|
|
kind: BuffKind::Burning,
|
|
|
|
dur_secs: 5.0,
|
|
|
|
strength: CombatBuffStrength::DamageFraction(0.2),
|
|
|
|
chance: 1.0,
|
|
|
|
}),
|
|
|
|
))],
|
|
|
|
BuffKind::Frigid => vec![BuffEffect::BuffOnHit(AttackEffect::new(
|
|
|
|
None,
|
|
|
|
CombatEffect::Buff(CombatBuff {
|
|
|
|
kind: BuffKind::Frozen,
|
|
|
|
dur_secs: 5.0,
|
|
|
|
strength: CombatBuffStrength::DamageFraction(0.2),
|
|
|
|
chance: 1.0,
|
|
|
|
}),
|
|
|
|
))],
|
|
|
|
BuffKind::Lifesteal => vec![BuffEffect::BuffOnHit(AttackEffect::new(
|
|
|
|
None,
|
|
|
|
CombatEffect::Lifesteal(0.2),
|
|
|
|
))],
|
2023-03-29 23:11:59 +00:00
|
|
|
}
|
|
|
|
}
|
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,
|
2023-03-11 21:44:57 +00:00
|
|
|
pub duration: Option<Secs>,
|
|
|
|
pub delay: Option<Secs>,
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
|
|
|
|
2020-12-04 22:24:56 +00:00
|
|
|
impl BuffData {
|
2023-03-11 21:44:57 +00:00
|
|
|
pub fn new(strength: f32, duration: Option<Secs>, delay: Option<Secs>) -> Self {
|
2023-01-14 00:48:59 +00:00
|
|
|
Self {
|
|
|
|
strength,
|
|
|
|
duration,
|
|
|
|
delay,
|
|
|
|
}
|
|
|
|
}
|
2020-12-04 22:24:56 +00:00
|
|
|
}
|
|
|
|
|
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,
|
2023-03-10 01:27:54 +00:00
|
|
|
FromActiveAura(Uid, AuraKey),
|
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,
|
2020-12-04 22:24:56 +00:00
|
|
|
Fractional,
|
2020-10-24 23:07:38 +00:00
|
|
|
}
|
|
|
|
|
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-12-04 22:24:56 +00:00
|
|
|
HealthChangeOverTime {
|
|
|
|
rate: f32,
|
|
|
|
kind: ModifierKind,
|
2022-04-30 20:39:20 +00:00
|
|
|
instance: u64,
|
2023-03-28 02:47:18 +00:00
|
|
|
tick_dur: Secs,
|
2020-12-04 22:24:56 +00:00
|
|
|
},
|
2021-09-02 23:57:17 +00:00
|
|
|
/// Periodically consume entity energy
|
2023-03-28 02:47:18 +00:00
|
|
|
EnergyChangeOverTime {
|
|
|
|
rate: f32,
|
|
|
|
kind: ModifierKind,
|
|
|
|
tick_dur: Secs,
|
|
|
|
},
|
2020-10-24 23:07:38 +00:00
|
|
|
/// Changes maximum health by a certain amount
|
2021-03-04 03:43:11 +00:00
|
|
|
MaxHealthModifier { value: f32, kind: ModifierKind },
|
2021-07-31 05:03:12 +00:00
|
|
|
/// Changes maximum energy by a certain amount
|
2021-03-04 03:43:11 +00:00
|
|
|
MaxEnergyModifier { value: f32, kind: ModifierKind },
|
|
|
|
/// Reduces damage after armor is accounted for by this fraction
|
|
|
|
DamageReduction(f32),
|
2021-03-20 17:29:57 +00:00
|
|
|
/// Gradually changes an entities max health over time
|
|
|
|
MaxHealthChangeOverTime {
|
|
|
|
rate: f32,
|
|
|
|
kind: ModifierKind,
|
|
|
|
target_fraction: f32,
|
|
|
|
},
|
2021-04-16 17:44:11 +00:00
|
|
|
/// Modifies move speed of target
|
|
|
|
MovementSpeed(f32),
|
2021-05-30 16:40:11 +00:00
|
|
|
/// Modifies attack speed of target
|
|
|
|
AttackSpeed(f32),
|
2021-05-24 00:45:22 +00:00
|
|
|
/// Modifies ground friction of target
|
|
|
|
GroundFriction(f32),
|
2022-03-05 20:51:41 +00:00
|
|
|
/// Reduces poise damage taken after armor is accounted for by this fraction
|
|
|
|
PoiseReduction(f32),
|
2023-01-14 00:48:59 +00:00
|
|
|
/// Reduces amount healed by consumables
|
2023-03-09 04:10:24 +00:00
|
|
|
HealReduction(f32),
|
2022-12-16 02:51:03 +00:00
|
|
|
/// Increases poise damage dealt when health is lost
|
|
|
|
PoiseDamageFromLostHealth { initial_health: f32, strength: f32 },
|
2022-12-19 18:26:03 +00:00
|
|
|
/// Modifier to the amount of damage dealt with attacks
|
|
|
|
AttackDamage(f32),
|
2023-02-11 00:31:07 +00:00
|
|
|
/// Multiplies crit chance of attacks
|
|
|
|
CriticalChance(f32),
|
2023-03-29 23:11:59 +00:00
|
|
|
/// Changes body.
|
|
|
|
BodyChange(Body),
|
2023-05-19 03:07:44 +00:00
|
|
|
/// Inflict buff to target
|
|
|
|
BuffOnHit(AttackEffect),
|
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>,
|
2023-03-08 03:22:54 +00:00
|
|
|
pub end_time: Option<Time>,
|
|
|
|
pub start_time: Time,
|
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
|
|
|
},
|
2023-04-07 06:10:24 +00:00
|
|
|
/// Refreshes durations of all buffs with this kind.
|
2022-11-16 00:46:34 +00:00
|
|
|
Refresh(BuffKind),
|
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,
|
2023-03-08 03:22:54 +00:00
|
|
|
time: Time,
|
2023-03-09 04:10:24 +00:00
|
|
|
stats: Option<&Stats>,
|
2022-12-16 02:51:03 +00:00
|
|
|
health: Option<&Health>,
|
2020-10-24 20:12:37 +00:00
|
|
|
) -> Self {
|
2023-03-29 23:11:59 +00:00
|
|
|
let effects = kind.effects(&data, stats, health);
|
2023-03-11 21:44:57 +00:00
|
|
|
let start_time = Time(time.0 + data.delay.map_or(0.0, |delay| delay.0));
|
2023-03-10 01:27:54 +00:00
|
|
|
let end_time = if cat_ids
|
|
|
|
.iter()
|
|
|
|
.any(|cat_id| matches!(cat_id, BuffCategory::FromActiveAura(..)))
|
|
|
|
{
|
|
|
|
None
|
|
|
|
} else {
|
2023-03-11 21:44:57 +00:00
|
|
|
data.duration.map(|dur| Time(start_time.0 + dur.0))
|
2023-03-10 01:27:54 +00:00
|
|
|
};
|
2020-10-24 20:12:37 +00:00
|
|
|
Buff {
|
|
|
|
kind,
|
|
|
|
data,
|
|
|
|
cat_ids,
|
2023-03-08 03:22:54 +00:00
|
|
|
start_time,
|
2023-03-10 01:27:54 +00:00
|
|
|
end_time,
|
2020-10-24 20:12:37 +00:00
|
|
|
effects,
|
|
|
|
source,
|
|
|
|
}
|
|
|
|
}
|
2023-01-19 00:11:55 +00:00
|
|
|
|
2023-03-08 03:22:54 +00:00
|
|
|
/// Calculate how much time has elapsed since the buff was applied
|
2023-03-11 21:44:57 +00:00
|
|
|
pub fn elapsed(&self, time: Time) -> Secs { Secs(time.0 - self.start_time.0) }
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
|
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)
|
2023-03-09 02:39:28 +00:00
|
|
|
} else if self.data.delay.is_none() && other.data.delay.is_some() {
|
|
|
|
Some(Ordering::Greater)
|
|
|
|
} else if self.data.delay.is_some() && other.data.delay.is_none() {
|
|
|
|
Some(Ordering::Less)
|
2023-03-08 03:22:54 +00:00
|
|
|
} else if compare_end_time(self.end_time, other.end_time) {
|
2020-10-25 01:20:03 +00:00
|
|
|
Some(Ordering::Greater)
|
2023-03-08 03:22:54 +00:00
|
|
|
} else if compare_end_time(other.end_time, self.end_time) {
|
2020-10-25 01:20:03 +00:00
|
|
|
Some(Ordering::Less)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-03-08 03:22:54 +00:00
|
|
|
fn compare_end_time(a: Option<Time>, b: Option<Time>) -> bool {
|
|
|
|
a.map_or(true, |time_a| b.map_or(false, |time_b| time_a.0 > time_b.0))
|
2020-10-25 01:20:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl PartialEq for Buff {
|
|
|
|
fn eq(&self, other: &Self) -> bool {
|
2023-03-12 20:37:02 +00:00
|
|
|
self.data.strength == other.data.strength
|
|
|
|
&& self.end_time == other.end_time
|
|
|
|
&& self.start_time == other.start_time
|
2020-10-25 01:20:03 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-10 22:54:45 +00:00
|
|
|
/// Source of the de/buff
|
2022-09-08 19:51:02 +00:00
|
|
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Serialize, Deserialize)]
|
2020-08-10 22:54:45 +00:00
|
|
|
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,
|
2023-03-12 20:37:02 +00:00
|
|
|
/// Maps Kinds of buff to Id's of currently applied buffs of that kind and
|
|
|
|
/// the time that the first buff was added (time gets reset if entity no
|
|
|
|
/// longer has buffs of that kind)
|
|
|
|
pub kinds: HashMap<BuffKind, (Vec<BuffId>, Time)>,
|
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) {
|
2023-03-12 20:37:02 +00:00
|
|
|
if buff_order.0.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
|
2021-04-25 16:01:21 +00:00
|
|
|
buff_order
|
2023-03-12 20:37:02 +00:00
|
|
|
.0
|
2021-07-11 18:41:52 +00:00
|
|
|
.sort_by(|a, b| buffs[b].partial_cmp(&buffs[a]).unwrap_or(Ordering::Equal));
|
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) {
|
2023-03-12 20:37:02 +00:00
|
|
|
for id in &buff_ids.0 {
|
2020-10-24 20:12:37 +00:00
|
|
|
self.buffs.remove(id);
|
|
|
|
}
|
|
|
|
self.kinds.remove(&kind);
|
2020-10-01 17:33:35 +00:00
|
|
|
}
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
|
2023-03-09 02:39:28 +00:00
|
|
|
fn force_insert(&mut self, id: BuffId, buff: Buff, current_time: Time) -> BuffId {
|
2020-10-24 20:12:37 +00:00
|
|
|
let kind = buff.kind;
|
2023-03-12 20:37:02 +00:00
|
|
|
self.kinds
|
|
|
|
.entry(kind)
|
|
|
|
.or_insert((Vec::new(), current_time))
|
|
|
|
.0
|
|
|
|
.push(id);
|
2020-10-24 20:12:37 +00:00
|
|
|
self.buffs.insert(id, buff);
|
|
|
|
self.sort_kind(kind);
|
2023-03-09 04:10:24 +00:00
|
|
|
if kind.queues() {
|
|
|
|
self.delay_queueable_buffs(kind, current_time);
|
|
|
|
}
|
2020-10-24 20:12:37 +00:00
|
|
|
id
|
|
|
|
}
|
|
|
|
|
2023-03-09 02:39:28 +00:00
|
|
|
pub fn insert(&mut self, buff: Buff, current_time: Time) -> BuffId {
|
2020-10-24 20:12:37 +00:00
|
|
|
self.id_counter += 1;
|
2023-03-09 02:39:28 +00:00
|
|
|
self.force_insert(self.id_counter, buff, current_time)
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
|
2020-12-04 22:24:56 +00:00
|
|
|
pub fn contains(&self, kind: BuffKind) -> bool { self.kinds.contains_key(&kind) }
|
|
|
|
|
2020-10-24 20:12:37 +00:00
|
|
|
// 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)
|
2023-03-12 20:37:02 +00:00
|
|
|
.map(|ids| ids.0.iter())
|
2022-09-08 19:51:02 +00:00
|
|
|
.unwrap_or_else(|| [].iter())
|
2020-10-24 20:12:37 +00:00
|
|
|
.map(move |id| (*id, &self.buffs[id]))
|
|
|
|
}
|
|
|
|
|
2023-01-14 00:48:59 +00:00
|
|
|
// Iterates through all active buffs (the most powerful buff of each
|
|
|
|
// non-stacking kind, and all of the stacking ones)
|
2023-01-15 22:26:18 +00:00
|
|
|
pub fn iter_active(&self) -> impl Iterator<Item = impl Iterator<Item = &Buff>> + '_ {
|
|
|
|
self.kinds.iter().map(move |(kind, ids)| {
|
2023-01-14 00:48:59 +00:00
|
|
|
if kind.stacks() {
|
2023-01-16 00:19:02 +00:00
|
|
|
// Iterate stackable buffs in reverse order to show the timer of the soonest one
|
|
|
|
// to expire
|
2023-03-12 20:37:02 +00:00
|
|
|
Either::Left(ids.0.iter().filter_map(|id| self.buffs.get(id)).rev())
|
2023-01-14 00:48:59 +00:00
|
|
|
} else {
|
2023-03-12 20:37:02 +00:00
|
|
|
Either::Right(self.buffs.get(&ids.0[0]).into_iter())
|
2023-01-14 00:48:59 +00:00
|
|
|
}
|
|
|
|
})
|
2020-10-24 20:12:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Gets most powerful buff of a given kind
|
|
|
|
pub fn remove(&mut self, buff_id: BuffId) {
|
2021-04-25 16:01:21 +00:00
|
|
|
if let Some(kind) = self.buffs.remove(&buff_id) {
|
|
|
|
let kind = kind.kind;
|
|
|
|
self.kinds
|
|
|
|
.get_mut(&kind)
|
2023-03-12 20:37:02 +00:00
|
|
|
.map(|ids| ids.0.retain(|id| *id != buff_id));
|
2021-04-25 16:01:21 +00:00
|
|
|
self.sort_kind(kind);
|
|
|
|
}
|
2020-10-01 17:33:35 +00:00
|
|
|
}
|
2021-02-20 20:37:46 +00:00
|
|
|
|
2023-03-09 02:39:28 +00:00
|
|
|
fn delay_queueable_buffs(&mut self, kind: BuffKind, current_time: Time) {
|
2023-03-08 03:22:54 +00:00
|
|
|
let mut next_start_time: Option<Time> = None;
|
2023-03-09 04:10:24 +00:00
|
|
|
debug_assert!(kind.queues());
|
2023-03-08 03:22:54 +00:00
|
|
|
if let Some(buffs) = self.kinds.get(&kind) {
|
2023-03-12 20:37:02 +00:00
|
|
|
buffs.0.iter().for_each(|id| {
|
2023-03-08 03:22:54 +00:00
|
|
|
if let Some(buff) = self.buffs.get_mut(id) {
|
2023-03-09 02:39:28 +00:00
|
|
|
// End time only being updated when there is some next_start_time will
|
|
|
|
// technically cause buffs to "end early" if they have a weaker strength than a
|
|
|
|
// buff with an infinite duration, but this is fine since those buffs wouldn't
|
|
|
|
// matter anyways
|
2023-03-08 03:22:54 +00:00
|
|
|
if let Some(next_start_time) = next_start_time {
|
2023-03-09 02:39:28 +00:00
|
|
|
// Delays buff so that it has the same progress it has now at the time the
|
|
|
|
// previous buff would end.
|
|
|
|
//
|
|
|
|
// Shift should be relative to current time, unless the buff is delayed and
|
|
|
|
// hasn't started yet
|
|
|
|
let reference_time = current_time.0.max(buff.start_time.0);
|
|
|
|
// If buff has a delay, ensure that queueables shuffling queue does not
|
|
|
|
// potentially allow skipping delay
|
|
|
|
buff.start_time = Time(next_start_time.0.max(buff.start_time.0));
|
|
|
|
buff.end_time = buff.end_time.map(|end| {
|
|
|
|
Time(end.0 + next_start_time.0.max(reference_time) - reference_time)
|
|
|
|
});
|
2023-03-08 03:22:54 +00:00
|
|
|
}
|
|
|
|
next_start_time = buff.end_time;
|
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
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 {
|
2022-08-08 04:38:20 +00:00
|
|
|
type Storage = DerefFlaggedStorage<Self, VecStorage<Self>>;
|
2020-08-10 22:54:45 +00:00
|
|
|
}
|
2023-03-09 02:39:28 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub mod tests {
|
|
|
|
use crate::comp::buff::*;
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
fn create_test_queueable_buff(buff_data: BuffData, time: Time) -> Buff {
|
|
|
|
// Change to another buff that queues if we ever add one and remove saturation,
|
|
|
|
// otherwise maybe add a test buff kind?
|
|
|
|
debug_assert!(BuffKind::Saturation.queues());
|
|
|
|
Buff::new(
|
|
|
|
BuffKind::Saturation,
|
|
|
|
buff_data,
|
|
|
|
Vec::new(),
|
|
|
|
BuffSource::Unknown,
|
|
|
|
time,
|
2023-03-11 21:44:57 +00:00
|
|
|
None,
|
2023-03-22 00:23:46 +00:00
|
|
|
None,
|
2023-03-09 02:39:28 +00:00
|
|
|
)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
/// Tests a number of buffs with various progresses that queue to ensure
|
|
|
|
/// queue has correct total duration
|
|
|
|
fn test_queueable_buffs_three() {
|
|
|
|
let mut buff_comp: Buffs = Default::default();
|
2023-03-11 21:44:57 +00:00
|
|
|
let buff_data = BuffData::new(1.0, Some(Secs(10.0)), None);
|
2023-03-09 02:39:28 +00:00
|
|
|
let time_a = Time(0.0);
|
|
|
|
buff_comp.insert(create_test_queueable_buff(buff_data, time_a), time_a);
|
|
|
|
let time_b = Time(6.0);
|
|
|
|
buff_comp.insert(create_test_queueable_buff(buff_data, time_b), time_b);
|
|
|
|
let time_c = Time(11.0);
|
|
|
|
buff_comp.insert(create_test_queueable_buff(buff_data, time_c), time_c);
|
|
|
|
// Check that all buffs have an end_time less than or equal to 30, and that at
|
|
|
|
// least one has an end_time greater than or equal to 30.
|
|
|
|
//
|
|
|
|
// This should be true because 3 buffs that each lasted for 10 seconds were
|
|
|
|
// inserted at various times, so the total duration should be 30 seconds.
|
|
|
|
assert!(
|
|
|
|
buff_comp
|
|
|
|
.buffs
|
|
|
|
.values()
|
|
|
|
.all(|b| b.end_time.unwrap().0 < 30.01)
|
|
|
|
);
|
|
|
|
assert!(
|
|
|
|
buff_comp
|
|
|
|
.buffs
|
|
|
|
.values()
|
|
|
|
.any(|b| b.end_time.unwrap().0 > 29.99)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
/// Tests that if a buff had a delay but will start soon, and an immediate
|
|
|
|
/// queueable buff is added, delayed buff has correct start time
|
|
|
|
fn test_queueable_buff_delay_start() {
|
|
|
|
let mut buff_comp: Buffs = Default::default();
|
2023-03-11 21:44:57 +00:00
|
|
|
let queued_buff_data = BuffData::new(1.0, Some(Secs(10.0)), Some(Secs(10.0)));
|
|
|
|
let buff_data = BuffData::new(1.0, Some(Secs(10.0)), None);
|
2023-03-09 02:39:28 +00:00
|
|
|
let time_a = Time(0.0);
|
|
|
|
buff_comp.insert(create_test_queueable_buff(queued_buff_data, time_a), time_a);
|
|
|
|
let time_b = Time(6.0);
|
|
|
|
buff_comp.insert(create_test_queueable_buff(buff_data, time_b), time_b);
|
|
|
|
// Check that all buffs have an end_time less than or equal to 26, and that at
|
|
|
|
// least one has an end_time greater than or equal to 26.
|
|
|
|
//
|
|
|
|
// This should be true because the first buff added had a delay of 10 seconds
|
|
|
|
// and a duration of 10 seconds, the second buff added at 6 seconds had no
|
|
|
|
// delay, and a duration of 10 seconds. When it finishes at 16 seconds the first
|
|
|
|
// buff is past the delay time so should finish at 26 seconds.
|
|
|
|
assert!(
|
|
|
|
buff_comp
|
|
|
|
.buffs
|
|
|
|
.values()
|
|
|
|
.all(|b| b.end_time.unwrap().0 < 26.01)
|
|
|
|
);
|
|
|
|
assert!(
|
|
|
|
buff_comp
|
|
|
|
.buffs
|
|
|
|
.values()
|
|
|
|
.any(|b| b.end_time.unwrap().0 > 25.99)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
/// Tests that if a buff had a long delay, a short immediate queueable buff
|
|
|
|
/// does not move delayed buff start or end times
|
|
|
|
fn test_queueable_buff_long_delay() {
|
|
|
|
let mut buff_comp: Buffs = Default::default();
|
2023-03-11 21:44:57 +00:00
|
|
|
let queued_buff_data = BuffData::new(1.0, Some(Secs(10.0)), Some(Secs(50.0)));
|
|
|
|
let buff_data = BuffData::new(1.0, Some(Secs(10.0)), None);
|
2023-03-09 02:39:28 +00:00
|
|
|
let time_a = Time(0.0);
|
|
|
|
buff_comp.insert(create_test_queueable_buff(queued_buff_data, time_a), time_a);
|
|
|
|
let time_b = Time(10.0);
|
|
|
|
buff_comp.insert(create_test_queueable_buff(buff_data, time_b), time_b);
|
|
|
|
// Check that all buffs have either an end time less than or equal to 20 seconds
|
|
|
|
// XOR a start time greater than or equal to 50 seconds, that all buffs have a
|
|
|
|
// start time less than or equal to 50 seconds, that all buffs have an end time
|
|
|
|
// less than or equal to 60 seconds, and that at least one buff has an end time
|
|
|
|
// greater than or equal to 60 seconds
|
|
|
|
//
|
|
|
|
// This should be true because the first buff has a delay of 50 seconds, the
|
|
|
|
// second buff added has no delay at 10 seconds and lasts 10 seconds, so should
|
|
|
|
// end at 20 seconds and not affect the start time of the delayed buff, and
|
|
|
|
// since the delayed buff was not affected the end time should be 10 seconds
|
|
|
|
// after the start time: 60 seconds != used here to emulate xor
|
|
|
|
assert!(
|
|
|
|
buff_comp
|
|
|
|
.buffs
|
|
|
|
.values()
|
|
|
|
.all(|b| (b.end_time.unwrap().0 < 20.01) != (b.start_time.0 > 49.99))
|
|
|
|
);
|
|
|
|
assert!(buff_comp.buffs.values().all(|b| b.start_time.0 < 50.01));
|
|
|
|
assert!(
|
|
|
|
buff_comp
|
|
|
|
.buffs
|
|
|
|
.values()
|
|
|
|
.all(|b| b.end_time.unwrap().0 < 60.01)
|
|
|
|
);
|
|
|
|
assert!(
|
|
|
|
buff_comp
|
|
|
|
.buffs
|
|
|
|
.values()
|
|
|
|
.any(|b| b.end_time.unwrap().0 > 59.99)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|