#![allow(clippy::nonstandard_macro_braces)] //tmp as of false positive !? use crate::{ combat::{AttackEffect, CombatBuff, CombatBuffStrength, CombatEffect}, comp::{aura::AuraKey, Health, Stats}, resources::{Secs, Time}, uid::Uid, }; use core::cmp::Ordering; use hashbrown::HashMap; use itertools::Either; use serde::{Deserialize, Serialize}; use specs::{Component, DerefFlaggedStorage, VecStorage}; use strum::EnumIter; use super::Body; /// De/buff Kind. /// This is used to determine what effects a buff will have #[derive( Clone, Copy, PartialEq, Eq, Hash, Debug, Serialize, Deserialize, PartialOrd, Ord, EnumIter, )] pub enum BuffKind { // Buffs /// Restores health/time for some period. /// Strength should be the healing per second. Regeneration, /// Restores health/time for some period for consumables. /// Strength should be the healing per second. Saturation, /// Applied when drinking a potion. /// Strength should be the healing per second. Potion, /// Applied when sitting at a campfire. /// Strength is fraction of health restored per second. CampfireHeal, /// Restores energy/time for some period. /// Strength should be the healing per second. EnergyRegen, /// Raises maximum energy. /// Strength should be 10x the effect to max energy. IncreaseMaxEnergy, /// Raises maximum health. /// Strength should be the effect to max health. IncreaseMaxHealth, /// Makes you immune to attacks. /// Strength does not affect this buff. Invulnerability, /// Reduces incoming damage. /// Strength scales the damage reduction non-linearly. 0.5 provides 50% DR, /// 1.0 provides 67% DR. ProtectingWard, /// Increases movement speed and gives health regeneration. /// Strength scales the movement speed linearly. 0.5 is 150% speed, 1.0 is /// 200% speed. Provides regeneration at 10x the value of the strength. Frenzied, /// 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. Hastened, /// Increases resistance to incoming poise, and poise damage dealt as health /// is lost from the time the buff activated. /// Strength scales the resistance non-linearly. 0.5 provides 50%, 1.0 /// provides 67%. /// 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 /// n%. Fortitude, /// Increases both attack damage and vulnerability to damage. /// Damage increases linearly with strength, 1.0 is a 100% increase. /// Damage reduction decreases linearly with strength, 1.0 is a 100% /// decrease. Reckless, // Debuffs /// Does damage to a creature over time. /// Strength should be the DPS of the debuff. Burning, /// Lowers health over time for some duration. /// Strength should be the DPS of the debuff. Bleeding, /// Lower a creature's max health over time. /// Strength only affects the target max health, 0.5 targets 50% of base /// max, 1.0 targets 100% of base max. Cursed, /// Reduces movement speed and causes bleeding damage. /// Strength scales the movement speed debuff non-linearly. 0.5 is 50% /// speed, 1.0 is 33% speed. Bleeding is at 4x the value of the strength. Crippled, /// 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 /// smaller than attack speed debuff. Frozen, /// 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, /// Makes you move slower. /// Strength scales the movement speed debuff non-linearly. 0.5 is 50% /// speed, 1.0 is 33% speed. Ensnared, /// Drain stamina to a creature over time. /// Strength should be the energy per second of the debuff. Poisoned, /// Results from having an attack parried. /// Causes your attack speed to be slower to emulate the recover duration of /// an ability being lengthened. Parried, /// Results from drinking a potion. /// Decreases the health gained from subsequent potions. PotionSickness, // Changed into another body. Polymorphed(Body), // Inflict burning on your attack Flame, // Inflict frost on your attack Frigid, // Gain Lifesteal on your attack Lifesteal, } impl BuffKind { /// Checks if buff is buff or debuff. pub fn is_buff(self) -> bool { match self { BuffKind::Regeneration | BuffKind::Saturation | BuffKind::Potion | BuffKind::CampfireHeal | BuffKind::Frenzied | BuffKind::EnergyRegen | BuffKind::IncreaseMaxEnergy | BuffKind::IncreaseMaxHealth | BuffKind::Invulnerability | BuffKind::ProtectingWard | BuffKind::Hastened | BuffKind::Fortitude | BuffKind::Reckless | BuffKind::Flame | BuffKind::Frigid | BuffKind::Lifesteal => true, BuffKind::Bleeding | BuffKind::Cursed | BuffKind::Burning | BuffKind::Crippled | BuffKind::Frozen | BuffKind::Wet | BuffKind::Ensnared | BuffKind::Poisoned | BuffKind::Parried | BuffKind::PotionSickness | BuffKind::Polymorphed(_) => false, } } /// Checks if buff should queue. pub fn queues(self) -> bool { matches!(self, BuffKind::Saturation) } /// 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) } pub fn effects( &self, data: &BuffData, stats: Option<&Stats>, health: Option<&Health>, ) -> Vec { // 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, 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), }], BuffKind::Potion => { vec![BuffEffect::HealthChangeOverTime { rate: data.strength * stats.map_or(1.0, |s| s.heal_multiplier), kind: ModifierKind::Additive, instance, tick_dur: Secs(0.1), }] }, BuffKind::CampfireHeal => vec![BuffEffect::HealthChangeOverTime { rate: data.strength, kind: ModifierKind::Fractional, instance, tick_dur: Secs(2.0), }], 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, tick_dur: Secs(0.5), }, ], BuffKind::EnergyRegen => vec![BuffEffect::EnergyChangeOverTime { rate: data.strength, kind: ModifierKind::Additive, tick_dur: Secs(1.0), }], 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, tick_dur: Secs(0.25), }], BuffKind::Poisoned => vec![BuffEffect::EnergyChangeOverTime { rate: -data.strength, kind: ModifierKind::Additive, tick_dur: Secs(0.5), }], BuffKind::Crippled => vec![ BuffEffect::MovementSpeed(1.0 - nn_scaling(data.strength)), BuffEffect::HealthChangeOverTime { rate: -data.strength * 4.0, kind: ModifierKind::Additive, instance, tick_dur: Secs(0.5), }, ], BuffKind::Frenzied => vec![ BuffEffect::MovementSpeed(1.0 + data.strength), BuffEffect::HealthChangeOverTime { rate: data.strength * 10.0, kind: ModifierKind::Additive, instance, tick_dur: Secs(1.0), }, ], 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)], 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), ))], } } } // Struct used to store data relevant to a buff #[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)] pub struct BuffData { pub strength: f32, pub duration: Option, pub delay: Option, } impl BuffData { pub fn new(strength: f32, duration: Option, delay: Option) -> Self { Self { strength, duration, delay, } } } /// 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 BuffCategory { Natural, Physical, Magical, Divine, PersistOnDeath, FromActiveAura(Uid, AuraKey), } #[derive(Clone, Debug, Serialize, Deserialize)] pub enum ModifierKind { Additive, Fractional, } /// Data indicating and configuring behaviour of a de/buff. #[derive(Clone, Debug, Serialize, Deserialize)] pub enum BuffEffect { /// Periodically damages or heals entity HealthChangeOverTime { rate: f32, kind: ModifierKind, instance: u64, tick_dur: Secs, }, /// Periodically consume entity energy EnergyChangeOverTime { rate: f32, kind: ModifierKind, tick_dur: Secs, }, /// Changes maximum health by a certain amount MaxHealthModifier { value: f32, kind: ModifierKind }, /// Changes maximum energy by a certain amount MaxEnergyModifier { value: f32, kind: ModifierKind }, /// Reduces damage after armor is accounted for by this fraction DamageReduction(f32), /// Gradually changes an entities max health over time MaxHealthChangeOverTime { rate: f32, kind: ModifierKind, target_fraction: f32, }, /// Modifies move speed of target MovementSpeed(f32), /// Modifies attack speed of target AttackSpeed(f32), /// Modifies ground friction of target GroundFriction(f32), /// Reduces poise damage taken after armor is accounted for by this fraction PoiseReduction(f32), /// Reduces amount healed by consumables HealReduction(f32), /// Increases poise damage dealt when health is lost PoiseDamageFromLostHealth { initial_health: f32, strength: f32 }, /// Modifier to the amount of damage dealt with attacks AttackDamage(f32), /// Multiplies crit chance of attacks CriticalChance(f32), /// Changes body. BodyChange(Body), /// Inflict buff to target BuffOnHit(AttackEffect), } /// 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 /// uncursing). /// /// Buff has a kind, which is used to determine the effects in a builder /// function. /// /// To provide more classification info when needed, /// buff can be in one or more buff category. #[derive(Clone, Debug, Serialize, Deserialize)] pub struct Buff { pub kind: BuffKind, pub data: BuffData, pub cat_ids: Vec, pub end_time: Option