mnml/server/src/skill.rs

1640 lines
53 KiB
Rust

use rand::{thread_rng, Rng};
use uuid::Uuid;
use util::{IntPct};
use cryp::{Cryp, CrypEffect, EffectMeta, Stat};
use vbox::{Var};
pub fn resolve(skill: Skill, source: &mut Cryp, target: &mut Cryp, mut resolutions: Vec<Resolution>) -> Resolutions {
if let Some(disable) = source.disabled(skill) {
resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }));
return resolutions;
}
if target.is_ko() {
resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }));
return resolutions;
}
if target.affected(Effect::Reflect) {
// guard against overflow
if source.affected(Effect::Reflect) {
return resolutions;
}
resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill }));
return resolve(skill, target, source, resolutions);
}
// match self.category() == Category::Red {
// true => {
// if let Some(evasion) = target.evade(*self) {
// resolutions.push(evasion);
// return Event;
// }
// },
// false => (),
// }
resolutions = match skill {
Skill::Amplify => amplify(source, target, resolutions, Skill::Amplify), // increase magic damage
Skill::Attack => attack(source, target, resolutions, Skill::Attack),
Skill::Banish => banish(source, target, resolutions, Skill::Banish), // TODO prevent all actions
Skill::Blast => blast(source, target, resolutions, Skill::Blast),
Skill::Block => block(source, target, resolutions, Skill::Block),
Skill::Chaos => chaos(source, target, resolutions, Skill::Chaos),
Skill::Clutch => clutch(source, target, resolutions, Skill::Clutch),
Skill::Corrupt => corrupt(source, target, resolutions, Skill::Corrupt),
Skill::CorruptionTick => corruption_tick(source, target, resolutions, Skill::CorruptionTick),
Skill::Curse => curse(source, target, resolutions, Skill::Curse),
Skill::Decay => decay(source, target, resolutions, Skill::Decay), // dot
Skill::DecayTick => decay_tick(source, target, resolutions, Skill::DecayTick), // dot
Skill::Empower => empower(source, target, resolutions, Skill::Empower), // increased phys damage
Skill::Haste => haste(source, target, resolutions, Skill::Haste), // speed slow
Skill::Heal => heal(source, target, resolutions, Skill::Heal),
Skill::Hex => hex(source, target, resolutions, Skill::Hex),
Skill::Hostility => hostility(source, target, resolutions, Skill::Hostility),
Skill::Invert => invert(source, target, resolutions, Skill::Invert),
Skill::Injure => injure(source, target, resolutions, Skill::Injure),
Skill::Parry => parry(source, target, resolutions, Skill::Parry),
Skill::Purge => purge(source, target, resolutions, Skill::Purge), // dispel all buffs
Skill::Purify => purify(source, target, resolutions, Skill::Purify), // dispel all debuffs
Skill::Recharge => recharge(source, target, resolutions, Skill::Recharge), // target is immune to magic damage and fx
Skill::Reflect => reflect(source, target, resolutions, Skill::Reflect),
Skill::Riposte => panic!("riposte should not be caste"),
Skill::Ruin => ruin(source, target, resolutions, Skill::Ruin),
Skill::Shield => shield(source, target, resolutions, Skill::Shield), // target is immune to magic damage and fx
Skill::Silence => silence(source, target, resolutions, Skill::Silence), // target cannot cast spells
Skill::Siphon => siphon(source, target, resolutions, Skill::Siphon),
Skill::SiphonTick => siphon_tick(source, target, resolutions, Skill::SiphonTick), // hot
Skill::Slay => slay(source, target, resolutions, Skill::Slay), // hybrid dmg self heal
Skill::Slow => slow(source, target, resolutions, Skill::Slow), // speed slow
Skill::Snare => snare(source, target, resolutions, Skill::Snare),
Skill::Strangle => strangle(source, target, resolutions, Skill::Strangle),
Skill::StrangleTick => strangle_tick(source, target, resolutions, Skill::StrangleTick),
Skill::Strike => strike(source, target, resolutions, Skill::Strike),
Skill::StrikeII => strike(source, target, resolutions, Skill::StrikeII),
Skill::StrikeIII => strike(source, target, resolutions, Skill::StrikeIII),
Skill::Stun => stun(source, target, resolutions, Skill::Stun),
Skill::Taunt => taunt(source, target, resolutions, Skill::Taunt),
Skill::Throw => throw(source, target, resolutions, Skill::Throw), // no damage stun, adds vulnerable
Skill::Triage => triage(source, target, resolutions, Skill::Triage), // hot
Skill::TriageTick => triage_tick(source, target, resolutions, Skill::TriageTick), // hot
// -----------------
// Test
// -----------------
Skill::TestTouch => touch(source, target, resolutions, Skill::TestTouch),
Skill::TestStun => stun(source, target, resolutions, Skill::Stun),
Skill::TestBlock => block(source, target, resolutions, Skill::Block),
Skill::TestParry => parry(source, target, resolutions, Skill::Parry),
Skill::TestSiphon => siphon(source, target, resolutions, Skill::Siphon),
};
// if any event dealt damage to target cryp
// hit them with corruption
// on damage events
// todo not sure if this fucks up with multiple calls to resolve
// have to think
for r in resolutions.clone() {
match r.event {
Event::Damage { amount, skill, mitigation: _, colour: _ } => {
if target.affected(Effect::Corrupt) {
resolutions = corruption(target, source, resolutions, Skill::Corrupt);
}
if target.affected(Effect::Hostility) {
resolutions = hatred(source, target, resolutions, skill, amount, Skill::Hostility);
}
},
Event::Immunity { skill: _, immunity } => match immunity.contains(&Effect::Parry) {
true => resolutions = riposte(target, source, resolutions, Skill::Riposte),
false => (),
},
_ => (),
}
};
// i don't think we need to check the source being ko
if target.is_ko() {
resolutions.push(Resolution::new(source, target).event(Event::Ko { skill }));
target.effects.clear();
}
return resolutions;
}
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Cast {
pub id: Uuid,
pub source_player_id: Uuid,
pub source_cryp_id: Uuid,
pub target_cryp_id: Uuid,
pub skill: Skill,
pub speed: u64,
pub resolutions: Resolutions,
}
impl Cast {
pub fn new(source_cryp_id: Uuid, source_player_id: Uuid, target_cryp_id: Uuid, skill: Skill) -> Cast {
return Cast {
id: Uuid::new_v4(),
source_cryp_id,
source_player_id,
target_cryp_id,
skill,
speed: 0,
resolutions: vec![],
};
}
pub fn new_tick(source: &mut Cryp, target: &mut Cryp, skill: Skill) -> Cast {
Cast::new(source.id, source.account, target.id, skill)
}
pub fn used_cooldown(&self) -> bool {
return self.skill.base_cd().is_some();
}
}
pub type Disable = Vec<Effect>;
pub type Immunity = Vec<Effect>;
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct LogCryp {
pub id: Uuid,
pub name: String,
}
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Resolution {
pub source: LogCryp,
pub target: LogCryp,
pub event: Event,
}
impl Resolution {
fn new(source: &Cryp, target: &Cryp) -> Resolution {
Resolution {
source: LogCryp { id: source.id, name: source.name.clone() },
target: LogCryp { id: target.id, name: target.name.clone() },
event: Event::Incomplete,
}
}
fn event(mut self, e: Event) -> Resolution {
self.event = e;
self
}
}
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub enum Event {
Disable { skill: Skill, disable: Disable },
Immunity { skill: Skill, immunity: Immunity },
Damage { skill: Skill, amount: u64, mitigation: u64, colour: Category },
Healing { skill: Skill, amount: u64, overhealing: u64 },
Recharge { skill: Skill, red: u64, blue: u64 },
Inversion { skill: Skill },
Reflection { skill: Skill },
Effect { skill: Skill, effect: Effect, duration: u8 },
Skill { skill: Skill },
Removal { effect: Effect },
TargetKo { skill: Skill },
// skill not necessary but makes it neater as all events are arrays in js
Ko { skill: Skill },
Incomplete,
// not used
Evasion { skill: Skill, evasion_rating: u64 },
}
type Resolutions = Vec<Resolution>;
pub type Cooldown = Option<u8>;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Effect {
// physical
Stun,
Parry,
Block,
Bleed,
Leech,
Airborne,
Untouchable,
Deadly,
Vulnerable,
Fury,
Blind,
Snare,
Clutch,
Injured,
Reflect,
Empower,
Taunt,
Invert,
Strangle,
Strangling,
// magic
Hex,
Ruin,
Curse,
Banish,
Slow,
Haste,
Enslave,
Mesmerise,
Amplify,
Silence,
Wither, // Reduce green dmg (healing) taken
// corrupt is the buff that applies
// corruption the dmg debuff
Corrupt,
Corruption,
// hostility is the buff
// hatred is the increased damage
Hostility,
Hatred,
// magic immunity
Shield,
// effects over time
Triage,
Decay,
Regen,
Siphon,
SpeedSiphon,
SpeedIncrease,
Ko,
}
impl Effect {
pub fn immune(&self, skill: Skill) -> bool {
match self {
Effect::Parry => match skill.category() {
Category::Blue => false,
Category::Red => true,
_ => false,
},
Effect::Shield => match skill.category() {
Category::Blue => true,
Category::Red => false,
_ => false,
},
Effect::Strangle => skill != Skill::StrangleTick,
Effect::Strangling => match skill.category() {
Category::BlueTick => false,
Category::RedTick => false,
_ => true,
},
Effect::Banish => true,
Effect::Injured => match skill.category() {
Category::Green => true,
Category::GreenTick => true,
_ => false,
},
_ => false,
}
}
pub fn disables_skill(&self, skill: Skill) -> bool {
match self {
Effect::Stun => true,
Effect::Hex => true,
Effect::Ruin => true,
Effect::Banish => true,
Effect::Strangle => true,
Effect::Strangling => skill != Skill::StrangleTick,
Effect::Silence => match skill.category() {
Category::Blue => true,
Category::Red => false,
_ => false,
},
Effect::Snare => match skill.category() {
Category::Blue => false,
Category::Red => true,
_ => false,
},
Effect::Ko => match skill.category() {
Category::BlueTick => false,
Category::GreenTick => false,
_ => true,
},
_ => false,
}
}
pub fn modifications(&self) -> Vec<Stat> {
match self {
Effect::Empower => vec![Stat::RedDamage],
Effect::Vulnerable => vec![Stat::RedDamageTaken],
Effect::Block => vec![Stat::RedDamageTaken],
Effect::Hatred => vec![Stat::RedDamage, Stat::BlueDamage],
Effect::Amplify => vec![Stat::BlueDamage],
Effect::Curse => vec![Stat::BlueDamageTaken],
Effect::Wither => vec![Stat::GreenDamageTaken],
Effect::Haste => vec![Stat::Speed],
Effect::Slow => vec![Stat::Speed],
_ => vec![],
}
}
pub fn apply(&self, value: u64, meta: Option<EffectMeta>) -> u64 {
match self {
Effect::Empower => value << 1,
Effect::Vulnerable => value << 1,
Effect::Block => value >> 1,
Effect::Amplify => value << 1,
Effect::Curse => value << 1,
Effect::Haste => value << 1,
Effect::Slow => value >> 1,
Effect::Wither => value >> 1,
Effect::Hatred => value + match meta {
Some(EffectMeta::AddedDamage(d)) => d,
_ => panic!("hatred meta not damage"),
},
_ => {
println!("{:?} does not have a mod effect", self);
return value;
},
}
}
pub fn category(&self) -> Category {
match self {
// physical
Effect::Stun => Category::Debuff,
Effect::Block => Category::Buff,
Effect::Parry => Category::Buff,
Effect::Bleed => Category::Debuff,
Effect::Leech => Category::Debuff,
Effect::Airborne => Category::Debuff,
Effect::Untouchable => Category::Buff,
Effect::Deadly => Category::Buff,
Effect::Vulnerable => Category::Debuff,
Effect::Fury => Category::Buff,
Effect::Blind => Category::Debuff,
Effect::Snare => Category::Debuff,
Effect::Clutch => Category::Buff,
Effect::Taunt => Category::Buff,
Effect::Empower => Category::Buff,
Effect::Injured => Category::Debuff,
Effect::Strangle => Category::Debuff,
Effect::Strangling => Category::Buff,
// magic
Effect::Hex => Category::Debuff,
Effect::Ruin => Category::Debuff,
Effect::Curse => Category::Debuff,
Effect::Banish => Category::Debuff, // todo randomise
Effect::Slow => Category::Debuff,
Effect::Haste => Category::Buff,
Effect::Hatred => Category::Buff,
Effect::Reflect => Category::Buff,
Effect::Enslave => Category::Debuff,
Effect::Mesmerise => Category::Debuff,
Effect::Amplify => Category::Buff,
Effect::Silence => Category::Debuff,
Effect::Wither => Category::Debuff,
Effect::Corrupt => Category::Buff,
Effect::Corruption => Category::Debuff,
Effect::Hostility => Category::Buff,
// magic immunity
Effect::Shield => Category::Buff,
Effect::Invert => Category::Buff,
// effects over time
Effect::Triage => Category::Buff,
Effect::Decay => Category::Debuff,
Effect::Regen => Category::Buff,
Effect::Siphon => Category::Debuff,
Effect::SpeedSiphon => Category::Debuff,
Effect::SpeedIncrease => Category::Buff,
Effect::Ko => Category::Ko,
}
}
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Category {
Red,
Blue,
Green,
RedDamage,
BlueDamage,
GreenDamage,
RedTick,
BlueTick,
GreenTick,
Buff,
Debuff,
Ko,
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill {
Attack,
// -----------------
// Nature
// -----------------
Block, // reduce damage
Parry, // avoid all damage
Riposte,
Snare,
Injure,
Recharge,
Reflect,
Ruin,
Slay,
Clutch,
Taunt,
Invert,
Strangle,
StrangleTick,
Strike,
StrikeII,
StrikeIII,
Stun,
// Evade, // actively evade
// -----------------
// Nonviolence
// -----------------
Heal,
Triage, // hot
TriageTick,
Throw, // no damage stun, adds vulnerable
// Sleep,
// Nightmare,
// -------------------
// Destruction
// -------------------
Blast,
Amplify,
Decay, // dot
DecayTick, // dot
Siphon,
SiphonTick,
Curse,
Hostility,
Corrupt,
CorruptionTick,
// -----------------
// Purity
// -----------------
Empower,
Shield,
Silence,
Purify,
Purge,
// -----------------
// Chaos
// -----------------
Banish,
Chaos,
Hex,
Haste,
Slow,
// used by tests, no cd, no damage
TestTouch,
TestStun,
TestBlock,
TestParry,
TestSiphon,
}
impl Skill {
pub fn multiplier(&self) -> u64 {
match self {
// Attack Base
Skill::Attack => 100, // 1.0 to pass tests
Skill::Blast => 130, // BB
Skill::Chaos => 50, // BR
Skill::Heal => 120, //GG
Skill::Slay => 70, // RG
Skill::Strike => 110, //RR
Skill::StrikeII => 130,
Skill::StrikeIII => 150,
// Block Base
Skill::Purify => 45, //Green dmg (heal)
// Others
Skill::CorruptionTick => 80,
Skill::DecayTick => 25,
Skill::Riposte => 100,
Skill::SiphonTick => 30,
Skill::StrangleTick => 30,
Skill::TriageTick => 65,
_ => 100,
}
}
pub fn duration(&self) -> u8 {
match self {
Skill::Block => 1,
Skill::Parry => 1,
Skill::Clutch => 1,
Skill::Reflect => 1,
Skill::Injure => 2,
Skill::Strangle => 2,
Skill::Stun => 2,
Skill::Throw => 2,
Skill::Snare => 2,
Skill::Taunt => 1,
Skill::Empower => 2,
Skill::Invert => 1,
Skill::Hex => 2,
Skill::Ruin => 1,
Skill::Curse => 2,
Skill::Banish => 1,
Skill::Slow => 2,
Skill::Haste => 2,
Skill::Amplify => 2,
Skill::Silence => 2,
Skill::Hostility => 2, // Primary Buff
Skill::Corrupt => 2, // Primary Buff
Skill::Shield => 2,
Skill::Triage => 3,
Skill::Decay => 3,
Skill::Siphon => 2,
_ => {
println!("{:?} does not have a duration", self);
return 1;
},
}
}
pub fn secondary_duration(&self) -> u8 {
match self {
Skill::Hostility => 5, // Increased dmg buff
Skill::Corrupt => 3, // Damage over time
Skill::Throw => 3, // Inc dmg taken debuff
_ => {
println!("{:?} does not have a secondary duration", self);
return 1;
},
}
}
pub fn base_cd(&self) -> Cooldown {
match self {
Skill::Attack => None,
Skill::Strike => None,
Skill::StrikeII => None,
Skill::StrikeIII => None,
Skill::Block => None, // reduce damage
Skill::Parry => None, // avoid all damage
Skill::Riposte => None, // used on parry
Skill::Snare => Some(1),
Skill::Stun => Some(1),
Skill::Heal => None,
Skill::Triage => None, // hot
Skill::TriageTick => None,
Skill::Throw => Some(1), // no damage stun, adds vulnerable
Skill::Blast => None,
Skill::Chaos => None,
Skill::Amplify => Some(1),
Skill::Invert => Some(2),
Skill::Decay => Some(1), // dot
Skill::DecayTick => None,
Skill::Siphon => None,
Skill::SiphonTick => None,
Skill::Curse => Some(1),
Skill::Empower => Some(1),
Skill::Shield => None,
Skill::Silence => Some(1),
Skill::Purify => None,
Skill::Purge => None,
Skill::Banish => Some(1),
Skill::Hex => Some(1),
Skill::Haste => None,
Skill::Slow => None,
Skill::Reflect => Some(2),
Skill::Recharge => Some(2),
Skill::Ruin => Some(3),
Skill::Slay => None,
Skill::Strangle => Some(2),
Skill::StrangleTick => None,
Skill::Clutch => Some(2),
Skill::Taunt => Some(1),
Skill::Injure => Some(2),
Skill::Corrupt => Some(1),
Skill::CorruptionTick => None,
Skill::Hostility => Some(1),
// -----------------
// Test
// -----------------
Skill::TestTouch => None,
Skill::TestStun => None,
Skill::TestBlock => None,
Skill::TestSiphon => None,
Skill::TestParry => None,
}
}
pub fn category(&self) -> Category {
match self {
Skill::Attack => Category::Red,
Skill::Strike => Category::Red,
Skill::StrikeII => Category::Red,
Skill::StrikeIII => Category::Red,
Skill::Injure => Category::Red,
Skill::Strangle => Category::Red,
Skill::StrangleTick => Category::Red,
Skill::Block => Category::Red, // reduce damage
Skill::Parry => Category::Red, // avoid all damage
Skill::Riposte => Category::Red, // avoid all damage
Skill::Snare => Category::Red,
Skill::Clutch => Category::Red,
Skill::Stun => Category::Red,
Skill::Slay => Category::Red,
Skill::Taunt => Category::Red,
Skill::Heal => Category::Green,
Skill::Triage => Category::Green, // hot
Skill::TriageTick => Category::GreenTick, // hot
Skill::Throw => Category::Green,
Skill::Empower => Category::Green,
Skill::Shield => Category::Green,
Skill::Purify => Category::Green,
Skill::Recharge => Category::Green,
Skill::Reflect => Category::Green,
Skill::Haste => Category::Green,
Skill::Invert => Category::Green,
Skill::Blast => Category::Blue,
Skill::Chaos => Category::Blue,
Skill::Amplify => Category::Blue,
Skill::Decay => Category::Blue, // dot
Skill::DecayTick => Category::BlueTick, // hot
Skill::Siphon => Category::Blue,
Skill::SiphonTick => Category::BlueTick, // hot
Skill::Curse => Category::Blue,
Skill::Silence => Category::Blue,
Skill::Purge => Category::Blue,
Skill::Banish => Category::Blue,
Skill::Hex => Category::Blue,
Skill::Slow => Category::Blue,
Skill::Ruin => Category::Blue,
Skill::Hostility => Category::Blue,
Skill::Corrupt => Category::Blue,
Skill::CorruptionTick => Category::Blue,
// -----------------
// Test
// -----------------
Skill::TestTouch => Category::Red,
Skill::TestStun => Category::Red,
Skill::TestParry => Category::Red,
Skill::TestBlock => Category::Red,
Skill::TestSiphon => Category::Blue,
}
}
pub fn ko_castable(&self) -> bool {
match self {
Skill::TriageTick => true,
Skill::DecayTick => true,
Skill::SiphonTick => true,
Skill::CorruptionTick => true,
_ => false,
}
}
pub fn speed(&self) -> u8 {
match self {
// -----------------
// Test
// -----------------
Skill::TestTouch => 10,
Skill::TestStun => 5,
Skill::TestBlock => 10,
Skill::TestParry => 10,
Skill::TestSiphon => 10,
Skill::Strike => u8::max_value(),
Skill::StrikeII => Skill::Strike.speed(),
Skill::StrikeIII => Skill::Strike.speed(),
Skill::SiphonTick => Var::from(Skill::Siphon).speed(),
Skill::DecayTick => Var::from(Skill::Decay).speed(),
Skill::TriageTick => Var::from(Skill::Triage).speed(),
Skill::StrangleTick => Var::from(Skill::Strangle).speed(),
Skill::CorruptionTick => Var::from(Skill::Corrupt).speed(),
_ => Var::from(*self).speed(),
}
}
pub fn aoe(&self) -> bool {
match self {
Skill::Ruin => true,
_ => false,
}
}
pub fn self_targeting(&self) -> bool {
match self {
Skill::Block => true,
Skill::Parry => true,
Skill::Clutch => true,
Skill::Taunt => true,
Skill::Corrupt => true,
Skill::TestBlock => true,
Skill::TestParry => true,
_ => false,
}
}
pub fn defensive(&self) -> bool {
let mut rng = thread_rng();
match self {
Skill::Heal |
Skill::Triage |
Skill::Empower |
Skill::Purify |
Skill::Parry |
Skill::Clutch |
Skill::Shield |
Skill::Recharge |
Skill::Reflect |
Skill::Haste |
Skill::Invert |
Skill::Amplify |
Skill::Hostility |
Skill::Corrupt |
Skill::Block => true,
Skill::Banish => rng.gen_bool(0.5),
_ => false,
}
}
}
fn touch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
target.deal_red_damage(skill, 0)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn attack(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_damage().pct(skill.multiplier());
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn strike(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_damage().pct(skill.multiplier());
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn injure(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_damage().pct(skill.multiplier());
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
let effect = CrypEffect::new(Effect::Injured, 2);
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;
}
fn stun(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Stun, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;
}
fn clutch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Clutch, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;
}
fn taunt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Taunt, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;
}
fn throw(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let stun = CrypEffect::new(Effect::Stun, skill.duration());
let vulnerable = CrypEffect::new(Effect::Vulnerable, skill.secondary_duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, stun)));
results.push(Resolution::new(source, target).event(target.add_effect(skill, vulnerable)));
return results;
}
fn strangle(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let target_stun = CrypEffect::new(Effect::Strangle, skill.duration())
.set_tick(Cast::new_tick(source, target, Skill::StrangleTick));
let attacker_immunity = CrypEffect::new(Effect::Strangling, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, target_stun)));
results.push(Resolution::new(source, source).event(source.add_effect(skill, attacker_immunity)));
return strangle_tick(source, target, results, Skill::StrangleTick);
}
fn strangle_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_damage().pct(skill.multiplier());
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
// remove immunity if target ko
if target.is_ko() {
let i = source.effects
.iter()
.position(|e| e.effect == Effect::Strangling)
.expect("no strangling on cryp");
source.effects.remove(i);
results.push(Resolution::new(source, source).event(Event::Removal { effect: Effect::Strangling }));
}
return results;
}
fn block(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let block = CrypEffect::new(Effect::Block, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, block)));
return results;
}
fn parry(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Parry, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;
}
fn riposte(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_damage().pct(skill.multiplier());
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn snare(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let snare = CrypEffect::new(Effect::Snare, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, snare)));
return results;
}
fn empower(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let empower = CrypEffect::new(Effect::Empower, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, empower)));
return results;
}
fn slay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_damage().pct(skill.multiplier());
let slay_events = target.deal_red_damage(skill, amount);
for e in slay_events {
match e {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
results.push(Resolution::new(source, target).event(e));
let heal = source.deal_green_damage(skill, amount);
for h in heal {
results.push(Resolution::new(source, source).event(h));
};
},
_ => results.push(Resolution::new(source, target).event(e)),
}
}
return results;
}
fn heal(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.green_damage().pct(skill.multiplier());
target.deal_green_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn triage(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Triage, skill.duration())
.set_tick(Cast::new_tick(source, target, Skill::TriageTick));
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return triage_tick(source, target, results, Skill::TriageTick);
}
fn triage_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.green_damage().pct(skill.multiplier());
target.deal_green_damage(Skill::TriageTick, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn chaos(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let mut rng = thread_rng();
let b_rng: u64 = rng.gen_range(0, 20);
let amount = source.blue_damage().pct(skill.multiplier() + b_rng);
target.deal_blue_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
let r_rng: u64 = rng.gen_range(0, 20);
let amount = source.red_damage().pct(skill.multiplier() + r_rng);
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn blast(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_damage().pct(skill.multiplier());
target.deal_blue_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn amplify(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amplify = CrypEffect::new(Effect::Amplify, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, amplify)));
return results;;
}
fn haste(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Haste, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;;
}
fn slow(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Slow, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;;
}
fn decay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let wither = CrypEffect::new(Effect::Wither, skill.duration());
let decay = CrypEffect::new(Effect::Decay, skill.duration())
.set_tick(Cast::new_tick(source, target, Skill::DecayTick));
results.push(Resolution::new(source, target).event(target.add_effect(skill, decay)));
results.push(Resolution::new(source, target).event(target.add_effect(skill, wither)));
return decay_tick(source, target, results, Skill::DecayTick);
}
fn decay_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_damage().pct(skill.multiplier());
target.deal_blue_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
// corrupt is the buff effect
// when attacked it runs corruption and applies a debuff
fn corrupt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Corrupt, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;;
}
fn corruption(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Corruption, skill.secondary_duration())
.set_tick(Cast::new_tick(source, target, Skill::CorruptionTick));
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return corruption_tick(source, target, results, Skill::CorruptionTick);
}
fn corruption_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_damage().pct(skill.multiplier());
target.deal_blue_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn ruin(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Ruin, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;;
}
fn hex(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let hex = CrypEffect::new(Effect::Hex, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, hex)));
return results;;
}
fn hostility(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Hostility, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;;
}
fn hatred(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Hatred, skill.secondary_duration())
.set_meta(EffectMeta::AddedDamage(amount));
results.push(Resolution::new(source, target).event(target.add_effect(reflect_skill, effect)));
return results;;
}
fn curse(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let curse = CrypEffect::new(Effect::Curse, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(Skill::Curse, curse)));
return results;;
}
fn invert(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Invert, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;;
}
fn reflect(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let effect = CrypEffect::new(Effect::Reflect, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
return results;;
}
fn recharge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.recharge()));
return results;
}
fn siphon(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let siphon = CrypEffect::new(Effect::Siphon, skill.duration())
.set_tick(Cast::new_tick(source, target, Skill::SiphonTick));
results.push(Resolution::new(source, target).event(target.add_effect(skill, siphon)));
return siphon_tick(source, target, results, Skill::SiphonTick);
}
fn siphon_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_damage().pct(skill.multiplier());
let siphon_events = target.deal_blue_damage(Skill::SiphonTick, amount);
for e in siphon_events {
match e {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
results.push(Resolution::new(source, target).event(e));
let heal = source.deal_green_damage(Skill::SiphonTick, amount);
for h in heal {
results.push(Resolution::new(source, source).event(h));
};
},
_ => results.push(Resolution::new(source, target).event(e)),
}
}
return results;
}
fn shield(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let shield = CrypEffect::new(Effect::Shield, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, shield)));
return results;
}
fn silence(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let silence = CrypEffect::new(Effect::Silence, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, silence)));
return results;
}
fn purge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
while let Some(i) = target.effects
.iter()
.position(|ce| [Category::Buff, Category::Buff].contains(&ce.effect.category())) {
let ce = target.effects.remove(i);
results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect }));
}
return results;
}
fn purify(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(Event::Skill { skill }));
let amount = source.green_damage().pct(skill.multiplier());
while let Some(i) = target.effects
.iter()
.position(|ce| [Category::Debuff, Category::Debuff].contains(&ce.effect.category())) {
let ce = target.effects.remove(i);
results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect }));
target.deal_green_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
}
return results;
}
fn banish(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let banish = CrypEffect::new(Effect::Banish, skill.duration());
results.push(Resolution::new(source, target).event(target.add_effect(skill, banish)));
return results;
}
#[cfg(test)]
mod tests {
use skill::*;
#[test]
fn heal_test() {
let mut x = Cryp::new()
.named(&"muji".to_string())
.learn(Skill::Heal);
let mut y = Cryp::new()
.named(&"camel".to_string())
.learn(Skill::Heal);
x.deal_red_damage(Skill::Attack, 5);
heal(&mut y, &mut x, vec![], Skill::Heal);
}
#[test]
fn decay_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
decay(&mut x, &mut y, vec![], Skill::Decay);
assert!(y.effects.iter().any(|e| e.effect == Effect::Decay));
y.reduce_effect_durations();
let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay);
// assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount));
}
#[test]
fn block_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
// ensure it doesn't have 0 pd
x.red_damage.force(100);
y.green_life.force(500);
block(&mut y.clone(), &mut y, vec![], Skill::Block);
assert!(y.effects.iter().any(|e| e.effect == Effect::Block));
let mut results = attack(&mut x, &mut y, vec![], Skill::Attack);
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 50),
_ => panic!("not damage"),
};
}
#[test]
fn clutch_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
x.red_damage.force(10000000000000); // multiplication of int max will cause overflow
clutch(&mut y.clone(), &mut y, vec![], Skill::Clutch);
assert!(y.affected(Effect::Clutch));
let mut results = attack(&mut x, &mut y, vec![], Skill::Attack);
assert!(y.green_life() == 1);
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023),
_ => panic!("not damage"),
};
}
#[test]
fn injure_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
resolve(Skill::Injure, &mut x, &mut y, vec![]);
assert!(y.immune(Skill::Heal).is_some());
// resolutions = heal(&mut y.clone(), &mut y, resolutions);
}
#[test]
fn invert_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
// give red shield but reduce to 0
y.red_life.force(64);
y.red_life.reduce(64);
x.red_damage.force(256 + 64);
let damage: u64 = x.red_damage().pct(Skill::Attack.multiplier()); // 320 * 1.0
let healing: u64 = x.green_damage().pct(Skill::Heal.multiplier()); // 256 * 1.2
invert(&mut y.clone(), &mut y, vec![], Skill::Invert);
assert!(y.affected(Effect::Invert));
// heal should deal green damage
heal(&mut x, &mut y, vec![], Skill::Heal);
assert!(y.green_life() == (1024 - healing));
// attack should heal and recharge red shield
let mut results = attack(&mut x, &mut y, vec![], Skill::Attack);
assert!(y.green_life() == 1024);
match results.remove(0).event {
Event::Inversion { skill } => assert_eq!(skill, Skill::Attack),
_ => panic!("not inversion"),
};
match results.remove(0).event {
Event::Healing { skill: _, overhealing: _, amount } => assert_eq!(amount, healing),
_ => panic!("not healing from inversion"),
};
match results.remove(0).event {
Event::Recharge { skill: _, red, blue: _ } => assert_eq!(red, (damage - healing)),
_ => panic!("not recharge from inversion"),
};
}
#[test]
fn reflect_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect);
assert!(y.affected(Effect::Reflect));
let mut results = vec![];
results = resolve(Skill::Attack, &mut x, &mut y, results);
assert!(x.green_life() == 768);
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Reflection { skill } => assert_eq!(skill, Skill::Attack),
_ => panic!("not reflection"),
};
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 256),
_ => panic!("not damage"),
};
}
#[test]
fn siphon_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
x.green_life.reduce(512);
let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]);
assert!(y.affected(Effect::Siphon));
assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier())));
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Effect { effect, skill: _, duration: _ } => assert_eq!(effect, Effect::Siphon),
_ => panic!("not siphon"),
};
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier())),
_ => panic!("not damage siphon"),
};
let Resolution { source: _, target, event } = results.remove(0);
match event {
Event::Healing { amount, skill: _, overhealing: _ } => {
assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()));
assert_eq!(target.id, x.id);
},
_ => panic!("not healing"),
};
}
#[test]
fn corrupt_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
corrupt(&mut y.clone(), &mut y, vec![], Skill::Corrupt);
assert!(y.affected(Effect::Corrupt));
resolve(Skill::Attack, &mut x, &mut y, vec![]);
assert!(x.affected(Effect::Corruption));
}
#[test]
fn hatred_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"camel".to_string());
hostility(&mut y.clone(), &mut y, vec![], Skill::Hostility);
assert!(y.affected(Effect::Hostility));
resolve(Skill::Attack, &mut x, &mut y, vec![]);
assert!(y.affected(Effect::Hatred));
let mut results = resolve(Skill::Attack, &mut y, &mut x, vec![]);
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 512),
_ => panic!("not damage hatred"),
};
}
#[test]
fn triage_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"pretaliation".to_string());
// ensure it doesn't have 0 sd
x.blue_damage.force(50);
// remove all mitigation
y.red_life.force(0);
y.blue_life.force(0);
y.deal_red_damage(Skill::Attack, 5);
let prev_hp = y.green_life();
triage(&mut x, &mut y, vec![], Skill::Triage);
assert!(y.effects.iter().any(|e| e.effect == Effect::Triage));
assert!(y.green_life() > prev_hp);
}
#[test]
fn recharge_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
let mut y = Cryp::new()
.named(&"pretaliation".to_string());
y.red_life.force(50);
y.blue_life.force(50);
y.deal_red_damage(Skill::Attack, 5);
y.deal_blue_damage(Skill::Blast, 5);
let mut results = recharge(&mut x, &mut y, vec![], Skill::Recharge);
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Recharge { red, blue, skill: _ } => {
assert!(red == 5);
assert!(blue == 5);
}
_ => panic!("result was not recharge"),
}
}
#[test]
fn silence_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
silence(&mut x.clone(), &mut x, vec![], Skill::Silence);
assert!(x.effects.iter().any(|e| e.effect == Effect::Silence));
assert!(x.disabled(Skill::Silence).is_some());
}
#[test]
fn amplify_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
x.blue_damage.force(50);
amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify);
assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify));
assert_eq!(x.blue_damage(), 100);
}
#[test]
fn purify_test() {
let mut x = Cryp::new()
.named(&"muji".to_string());
decay(&mut x.clone(), &mut x, vec![], Skill::Decay);
assert!(x.effects.iter().any(|e| e.effect == Effect::Decay));
purify(&mut x.clone(), &mut x, vec![], Skill::Purify);
assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay));
}
}
// pub enum Skill {
// Attack,
// // -----------------
// // Nature
// // -----------------
// Block, // reduce damage
// Parry, // avoid all damage
// Snare,
// Paralyse,
// Strangle, // physical dot and disable
// Strike,
// Stun,
// // Evade, // actively evade
// // -----------------
// // Technology
// // -----------------
// Replicate,
// Swarm,
// Orbit,
// Repair,
// Scan, // track?
// // -----------------
// // Nonviolence
// // -----------------
// Heal,
// Triage, // hot
// TriageTick,
// Throw, // no damage stun, adds vulnerable
// Charm,
// Calm,
// Rez,
// // Sleep,
// // Nightmare,
// // -------------------
// // Destruction
// // -------------------
// Blast,
// Amplify,
// Decay, // dot
// DecayTick, // dot
// Siphon,
// SiphonTick,
// Curse,
// Corrupt, // aoe dot
// Ruin, // aoe
// // -----------------
// // Purity
// // -----------------
// Empower,
// Slay,
// Shield,
// Silence,
// Inquiry,
// Purify,
// Purge,
// // Precision,
// // -----------------
// // Chaos
// // -----------------
// Banish,
// Hex,
// Fear,
// Taunt,
// Pause, // speed slow
// Haste,
// Slow,
// // used by tests, no cd, no damage
// TestTouch,
// TestStun,
// TestBlock,
// TestParry,
// TestSiphon,
// }