mnml/server/src/skill.rs
2019-05-27 10:41:36 +10:00

1879 lines
67 KiB
Rust

use rand::{thread_rng, Rng};
use uuid::Uuid;
use util::{IntPct};
use cryp::{Cryp, CrypEffect, EffectMeta, Stat};
use item::{Item};
use game::{Game};
pub fn resolution_steps(cast: &Cast, game: &mut Game) -> Resolutions {
let mut resolutions = vec![];
resolutions = pre_resolve(cast, game, resolutions);
return resolutions;
}
pub fn pre_resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) -> Resolutions {
let skill = cast.skill;
let source = game.cryp_by_id(cast.source_cryp_id).unwrap().clone();
let targets = game.get_targets(cast.skill, &source, cast.target_cryp_id);
if skill.aoe() { // Send an aoe skill event for anims
resolutions.push(Resolution::new(&source, &game.cryp_by_id(cast.target_cryp_id).unwrap().clone()).event(Event::AoeSkill { skill }));
}
for target_id in targets {
// we clone the current state of the target and source
// so we can modify them during the resolution
// no more than 1 mutable ref allowed on game
let mut source = game.cryp_by_id(cast.source_cryp_id).unwrap().clone();
let mut target = game.cryp_by_id(target_id).unwrap().clone();
// bail out on ticks that have been removed
if skill.is_tick() && target.effects.iter().find(|ce| match ce.tick {
Some(t) => t.id == cast.id,
None => false,
}).is_none() {
continue;
}
resolutions = resolve(cast.skill, &mut source, &mut target, resolutions);
// save the changes to the game
game.update_cryp(&mut source);
game.update_cryp(&mut target);
// do additional steps
resolutions = post_resolve(cast.skill, game, resolutions);
}
return resolutions;
}
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);
}
if source.affected(Effect::Haste) {
match skill {
Skill::Attack |
Skill::SlayI |
Skill::ChaosI |
Skill::StrikeI => {
let amount = source.speed().pct(Skill::HasteStrike.multiplier());
target.deal_red_damage(Skill::HasteStrike, amount)
.into_iter()
.for_each(|e| resolutions.push(Resolution::new(source, target).event(e)));
},
_ => (),
}
}
if source.affected(Effect::Impurity) {
match skill {
Skill::BlastI |
Skill::ChaosI |
Skill::Siphon => {
let amount = source.green_damage().pct(Skill::ImpureBlast.multiplier());
target.deal_blue_damage(Skill::ImpureBlast, amount)
.into_iter()
.for_each(|e| resolutions.push(Resolution::new(source, target).event(e)));
},
_ => (),
}
}
// match self.category() == EffectCategory::Red {
// true => {
// if let Some(evasion) = target.evade(*self) {
// resolutions.push(evasion);
// return Event;
// }
// },
// false => (),
// }
resolutions = match skill {
Skill::AmplifyI |
Skill::AmplifyII |
Skill::AmplifyIII => amplify(source, target, resolutions, skill),
Skill::Banish => banish(source, target, resolutions, skill), // TODO prevent all actions
Skill::BlastI |
Skill::BlastII |
Skill::BlastIII => blast(source, target, resolutions, skill),
Skill::ChaosI |
Skill::ChaosII |
Skill::ChaosIII => chaos(source, target, resolutions, skill),
Skill::ClutchI |
Skill::ClutchII |
Skill::ClutchIII => clutch(source, target, resolutions, skill),
Skill::Corrupt => corrupt(source, target, resolutions, skill),
Skill::CorruptionTick => corruption_tick(source, target, resolutions, skill),
Skill::CurseI |
Skill::CurseII |
Skill::CurseIII => curse(source, target, resolutions, skill),
Skill::Decay => decay(source, target, resolutions, skill), // dot
Skill::DecayTick => decay_tick(source, target, resolutions, skill), // dot
Skill::Haste => haste(source, target, resolutions, skill), // speed slow
Skill::HealI |
Skill::HealII |
Skill::HealIII => heal(source, target, resolutions, skill),
Skill::HexI |
Skill::HexII |
Skill::HexIII => hex(source, target, resolutions, skill),
Skill::Hostility => hostility(source, target, resolutions, skill),
Skill::Impurity => impurity(source, target, resolutions, skill),
Skill::InvertI |
Skill::InvertII |
Skill::InvertIII => invert(source, target, resolutions, skill),
Skill::Parry => parry(source, target, resolutions, skill),
Skill::PurgeI |
Skill::PurgeII |
Skill::PurgeIII => purge(source, target, resolutions, skill), // dispel all buffs
Skill::PurifyI |
Skill::PurifyII |
Skill::PurifyIII => purify(source, target, resolutions, skill),
Skill::RechargeI |
Skill::RechargeII |
Skill::RechargeIII => recharge(source, target, resolutions, skill),
Skill::ReflectI |
Skill::ReflectII |
Skill::ReflectIII => reflect(source, target, resolutions, skill),
Skill::RuinI |
Skill::RuinII |
Skill::RuinIII => ruin(source, target, resolutions, skill),
Skill::ScatterI |
Skill::ScatterII |
Skill::ScatterIII => scatter(source, target, resolutions, skill), // target is immune to magic damage and fx
Skill::SilenceI |
Skill::SilenceII |
Skill::SilenceIII => silence(source, target, resolutions, skill), // target cannot cast spells
Skill::Siphon => siphon(source, target, resolutions, skill),
Skill::SiphonTick => siphon_tick(source, target, resolutions, skill), // hot
Skill::SlayI |
Skill::SlayII |
Skill::SlayIII => slay(source, target, resolutions, skill), // hybrid dmg self heal
Skill::SleepI |
Skill::SleepII |
Skill::SleepIII => sleep(source, target, resolutions, skill), // heal stun
Skill::SnareI |
Skill::SnareII |
Skill::SnareIII => snare(source, target, resolutions, skill),
Skill::Strangle => strangle(source, target, resolutions, skill),
Skill::StrangleTick => strangle_tick(source, target, resolutions, skill),
Skill::StrikeI |
Skill::StrikeII |
Skill::StrikeIII => strike(source, target, resolutions, skill),
Skill::TauntI |
Skill::TauntII |
Skill::TauntIII => taunt(source, target, resolutions, skill),
Skill::ThrowI |
Skill::ThrowII |
Skill::ThrowIII => throw(source, target, resolutions, skill), // no damage stun, adds vulnerable
Skill::Triage => triage(source, target, resolutions, skill), // hot
Skill::TriageTick => triage_tick(source, target, resolutions, skill), // hot
// Base Skills
Skill::Attack => attack(source, target, resolutions, skill),
Skill::Block => block(source, target, resolutions, skill),
Skill::Buff => buff(source, target, resolutions, skill),
Skill::Debuff => debuff(source, target, resolutions, skill), // speed slow
Skill::Stun => stun(source, target, resolutions, skill),
//Triggered
Skill::HasteStrike => panic!("should only trigger from haste"),
Skill::ImpureBlast => panic!("should only trigger from impurity"),
Skill::Riposte => panic!("should only trigger from parry"),
// Not used
Skill::Injure => injure(source, target, resolutions, skill),
// -----------------
// Test
// -----------------
Skill::TestAttack => attack(source, target, resolutions, skill),
Skill::TestHeal => heal(source, target, resolutions, skill),
Skill::TestTouch => touch(source, target, resolutions, skill),
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),
};
return resolutions;
}
fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions {
for Resolution { source, target, event } in resolutions.clone() {
let mut source = game.cryp_by_id(source.id).unwrap().clone();
let mut target = game.cryp_by_id(target.id).unwrap().clone();
match event {
Event::Damage { amount, skill, mitigation: _, colour: _ } => {
if target.affected(Effect::Corrupt) {
resolutions = corruption(&mut target, &mut source, resolutions, Skill::Corrupt);
}
if target.affected(Effect::Hostility) {
resolutions = hatred(&mut source, &mut target, resolutions, skill, amount, Skill::Hostility);
}
// beware that scatter doesn't cause any damage
// because then applying it will proc this
if target.affected(Effect::Scatter) {
resolutions = scatter_hit(&source, &target, resolutions, game, event)
}
},
Event::Immunity { skill: _, immunity } => match immunity.contains(&Effect::Parry) {
true => {
resolutions = riposte(&mut target, &mut source, resolutions, Skill::Riposte);
}
false => (),
},
_ => (),
};
game.update_cryp(&mut source);
game.update_cryp(&mut target);
};
return resolutions;
}
#[derive(Debug,Clone,Copy,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,
}
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,
};
}
pub fn new_tick(source: &mut Cryp, target: &mut Cryp, skill: Skill) -> Cast {
Cast {
id: Uuid::new_v4(),
source_cryp_id: source.id,
source_player_id: source.account,
target_cryp_id: target.id,
skill,
speed: 0,
}
}
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: Colour },
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 },
AoeSkill { skill: Skill },
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)]
enum EffectCategory {
Buff,
Debuff,
Ko,
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Effect {
Amplify,
Banish,
Block,
Buff,
Clutch,
Curse,
Haste,
Hex,
Impurity,
Invert,
Parry,
Reflect,
Slow,
Snare,
Strangle,
Strangling,
Stun,
Taunt,
Vulnerable,
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
Scatter,
// effects over time
Triage,
Decay,
Regen,
Siphon,
Injured,
// Airborne,
// Boost
// Bleed,
// Blind,
// Deadly,
// Enslave,
// Fury,
// Injured,
// Leech,
// Mesmerise,
// Untouchable,
// SpeedSiphon,
// SpeedIncrease,
Ko,
}
impl Effect {
pub fn immune(&self, skill: Skill) -> bool {
match self {
Effect::Parry =>
skill.colours().contains(&Colour::Red) ||
[Skill::Attack, Skill::Stun].contains(&skill),
Effect::Banish => true,
Effect::Clutch => [
Skill::Stun,
Skill::HexI,
Skill::HexII,
Skill::HexIII,
Skill::SilenceI,
Skill::SilenceII,
Skill::SilenceIII,
Skill::RuinI,
Skill::RuinII,
Skill::RuinIII,
Skill::Strangle,
Skill::SnareI,
Skill::SnareII,
Skill::SnareIII
].contains(&skill),
Effect::Injured => skill.colours().contains(&Colour::Green),
_ => false,
}
}
pub fn disables_skill(&self, skill: Skill) -> bool {
if skill.is_tick() {
return false;
}
match self {
Effect::Stun => true,
Effect::Hex => true,
Effect::Banish => true,
Effect::Strangle => true,
Effect::Strangling => skill != Skill::StrangleTick,
Effect::Silence => skill.colours().contains(&Colour::Blue),
Effect::Snare => skill.colours().contains(&Colour::Red),
Effect::Ko => skill.ko_castable(),
_ => false,
}
}
pub fn modifications(&self) -> Vec<Stat> {
match self {
Effect::Vulnerable => vec![Stat::RedDamageTaken],
Effect::Block => vec![Stat::RedDamageTaken],
Effect::Buff => vec![Stat::RedDamage, Stat::Speed],
Effect::Hatred => vec![Stat::RedDamage, Stat::BlueDamage],
Effect::Amplify => vec![Stat::RedDamage, Stat::BlueDamage],
Effect::Curse => vec![Stat::BlueDamageTaken],
Effect::Impurity => vec![Stat::GreenDamage],
Effect::Wither => vec![Stat::GreenDamageTaken],
Effect::Haste => vec![Stat::Speed],
Effect::Slow => vec![Stat::Speed],
Effect::Scatter => vec![Stat::BlueDamageTaken, Stat::GreenDamageTaken, Stat::RedDamageTaken],
_ => vec![],
}
}
pub fn apply(&self, value: u64, meta: Option<EffectMeta>) -> u64 {
match self {
Effect::Amplify |
Effect::Vulnerable |
Effect::Block |
Effect::Buff |
Effect::Curse |
Effect::Haste |
Effect::Slow |
Effect::Impurity |
Effect::Wither => value.pct(match meta {
Some(EffectMeta::Multiplier(d)) => d,
_ => 100,
}),
Effect::Scatter => value >> 1,
Effect::Hatred => value + match meta {
Some(EffectMeta::AddedDamage(d)) => d,
_ => panic!("hatred meta not damage"),
},
_ => {
info!("{:?} does not have a mod effect", self);
return value;
},
}
}
fn category(&self) -> EffectCategory {
match self {
// physical
Effect::Stun => EffectCategory::Debuff,
Effect::Block => EffectCategory::Buff,
Effect::Buff => EffectCategory::Buff,
Effect::Parry => EffectCategory::Buff,
Effect::Vulnerable => EffectCategory::Debuff,
Effect::Snare => EffectCategory::Debuff,
Effect::Clutch => EffectCategory::Buff,
Effect::Taunt => EffectCategory::Buff,
Effect::Strangle => EffectCategory::Debuff,
Effect::Strangling => EffectCategory::Buff,
// magic
Effect::Hex => EffectCategory::Debuff,
Effect::Curse => EffectCategory::Debuff,
Effect::Banish => EffectCategory::Debuff, // todo randomise
// Effect::Banish => rng.gen_bool(0.5),
Effect::Slow => EffectCategory::Debuff,
Effect::Haste => EffectCategory::Buff,
Effect::Hatred => EffectCategory::Buff,
Effect::Reflect => EffectCategory::Buff,
Effect::Amplify => EffectCategory::Buff,
Effect::Silence => EffectCategory::Debuff,
Effect::Wither => EffectCategory::Debuff,
Effect::Corrupt => EffectCategory::Buff,
Effect::Corruption => EffectCategory::Debuff,
Effect::Hostility => EffectCategory::Buff,
// magic
Effect::Impurity => EffectCategory::Buff,
Effect::Scatter => EffectCategory::Buff,
Effect::Invert => EffectCategory::Buff,
// effects over time
Effect::Triage => EffectCategory::Buff,
Effect::Decay => EffectCategory::Debuff,
Effect::Regen => EffectCategory::Buff,
Effect::Siphon => EffectCategory::Debuff,
// not in game
Effect::Injured => EffectCategory::Debuff,
Effect::Ko => EffectCategory::Ko,
}
}
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Colour {
Red,
Blue,
Green,
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill {
Attack,
Debuff,
Buff,
Block, // reduce damage
Stun,
// Boost -- sounds nice
// Evade, // actively evade
// Nightmare,
// Sleep,
AmplifyI,
AmplifyII,
AmplifyIII,
Banish,
BlastI,
BlastII,
BlastIII,
ChaosI,
ChaosII,
ChaosIII,
ClutchI,
ClutchII,
ClutchIII,
Corrupt,
CorruptionTick,
CurseI,
CurseII,
CurseIII,
Decay, // dot
DecayTick, // dot
Haste,
HasteStrike,
HealI,
HealII,
HealIII,
HexI,
HexII,
HexIII,
Hostility,
ImpureBlast,
Impurity,
Injure,
InvertI,
InvertII,
InvertIII,
Parry, // avoid all damage
PurgeI,
PurgeII,
PurgeIII,
PurifyI,
PurifyII,
PurifyIII,
RechargeI,
RechargeII,
RechargeIII,
ReflectI,
ReflectII,
ReflectIII,
Riposte,
RuinI,
RuinII,
RuinIII,
ScatterI,
ScatterII,
ScatterIII,
SilenceI,
SilenceII,
SilenceIII,
Siphon,
SiphonTick,
SlayI,
SlayII,
SlayIII,
SleepI,
SleepII,
SleepIII,
SnareI,
SnareII,
SnareIII,
Strangle,
StrangleTick,
StrikeI,
StrikeII,
StrikeIII,
TauntI,
TauntII,
TauntIII,
ThrowI, // no damage stun, adds vulnerable
ThrowII,
ThrowIII,
Triage, // hot
TriageTick,
// used by tests, no cd, 100% multiplier
TestAttack,
TestHeal,
TestTouch, // No damage
TestStun,
TestBlock,
TestParry,
TestSiphon,
}
impl Skill {
pub fn multiplier(&self) -> u64 {
match self {
// Attack Base
Skill::Attack => 80, // Base
Skill::BlastI => 110, // BB
Skill::BlastII => 130, // BB
Skill::BlastIII => 150, // BB
Skill::ChaosI => 40, // BR
Skill::ChaosII => 50, // BR
Skill::ChaosIII => 60, // BR
Skill::HealI => 130, //GG
Skill::HealII => 160, //GG
Skill::HealIII => 200, //GG
Skill::SiphonTick => 40, // GB
Skill::SlayI => 70, // RG
Skill::SlayII => 90,
Skill::SlayIII => 120,
Skill::StrikeI => 90, //RR
Skill::StrikeII => 110,
Skill::StrikeIII => 140,
// Block Base
Skill::CorruptionTick => 80,
Skill::Parry => 110,
Skill::Riposte => 70,
Skill::PurifyI => 45, //Green dmg (heal)
Skill::PurifyII => 70,
Skill::PurifyIII => 105,
Skill::ReflectI => 45, //restore blue life (heal)
Skill::ReflectII => 70,
Skill::ReflectIII => 100,
Skill::RechargeI => 85, //restore red and blue life (heal)
Skill::RechargeII => 130,
Skill::RechargeIII => 200,
// Stun Base
Skill::SleepI => 240, //Green dmg (heal)
Skill::SleepII => 300,
Skill::SleepIII => 400,
Skill::StrangleTick => 65,
// Debuff Base
Skill::DecayTick => 25,
Skill::SilenceI => 55, // Deals more per blue skill on target
Skill::SilenceII => 80, // Deals more per blue skill on target
Skill::SilenceIII => 110, // Deals more per blue skill on target
Skill::SnareI => 40, // Deals more per red skill on target
Skill::SnareII => 65,
Skill::SnareIII => 100,
// Buff base
Skill::ImpureBlast => 25,
Skill::HasteStrike => 30,
Skill::ScatterI => 140,
Skill::ScatterII => 200,
Skill::ScatterIII => 300,
Skill::TauntI => 80,
Skill::TauntII => 110,
Skill::TauntIII => 150,
Skill::TriageTick => 75,
_ => 100,
}
}
pub fn effect(&self) -> Vec<CrypEffect> {
match self {
// Modifiers
Skill::AmplifyI => vec![CrypEffect {effect: Effect::Amplify, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None}],
Skill::AmplifyII => vec![CrypEffect {effect: Effect::Amplify, duration: 3, meta: Some(EffectMeta::Multiplier(175)), tick: None}],
Skill::AmplifyIII => vec![CrypEffect {effect: Effect::Amplify, duration: 4, meta: Some(EffectMeta::Multiplier(200)), tick: None}],
Skill::Banish => vec![CrypEffect {effect: Effect::Banish, duration: 1, meta: None, tick: None}],
Skill::Block => vec![CrypEffect {effect: Effect::Block, duration: 1, meta: Some(EffectMeta::Multiplier(50)), tick: None}],
Skill::Buff => vec![CrypEffect {effect: Effect::Buff, duration: 2, meta: Some(EffectMeta::Multiplier(125)), tick: None }],
Skill::Corrupt => vec![CrypEffect {effect: Effect::Corrupt, duration: 2, meta: None, tick: None},
CrypEffect {effect: Effect::Corruption, duration: 3, meta: None, tick: None}],
Skill::ClutchI => vec![CrypEffect {effect: Effect::Clutch, duration: 1, meta: None, tick: None }],
Skill::ClutchII => vec![CrypEffect {effect: Effect::Clutch, duration: 2, meta: None, tick: None }],
Skill::ClutchIII => vec![CrypEffect {effect: Effect::Clutch, duration: 3, meta: None, tick: None }],
Skill::CurseI => vec![CrypEffect {effect: Effect::Curse, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None}],
Skill::CurseII => vec![CrypEffect {effect: Effect::Curse, duration: 2, meta: Some(EffectMeta::Multiplier(200)), tick: None}],
Skill::CurseIII => vec![CrypEffect {effect: Effect::Curse, duration: 3, meta: Some(EffectMeta::Multiplier(250)), tick: None}],
Skill::Debuff => vec![CrypEffect {effect: Effect::Slow, duration: 3, meta: Some(EffectMeta::Multiplier(50)), tick: None }],
Skill::Decay => vec![CrypEffect {effect: Effect::Wither, duration: 3, meta: Some(EffectMeta::Multiplier(50)), tick: None },
CrypEffect {effect: Effect::Decay, duration: 3, meta: None, tick: None }],
Skill::Haste => vec![CrypEffect {effect: Effect::Haste, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None }],
Skill::HexI => vec![CrypEffect {effect: Effect::Hex, duration: 2, meta: None, tick: None}],
Skill::HexII => vec![CrypEffect {effect: Effect::Hex, duration: 3, meta: None, tick: None}],
Skill::HexIII => vec![CrypEffect {effect: Effect::Hex, duration: 4, meta: None, tick: None}],
Skill::Hostility => vec![CrypEffect {effect: Effect::Hostility, duration: 2, meta: None, tick: None},
CrypEffect {effect: Effect::Hatred, duration: 5, meta: None, tick: None}],
Skill::Impurity => vec![CrypEffect {effect: Effect::Impurity, duration: 3, meta: Some(EffectMeta::Multiplier(150)), tick: None }],
Skill::InvertI => vec![CrypEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}],
Skill::InvertII => vec![CrypEffect {effect: Effect::Invert, duration: 3, meta: None, tick: None}],
Skill::InvertIII => vec![CrypEffect {effect: Effect::Invert, duration: 4, meta: None, tick: None}],
Skill::Parry => vec![CrypEffect {effect: Effect::Parry, duration: 2, meta: None, tick: None }],
Skill::ReflectI => vec![CrypEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }],
Skill::ReflectII => vec![CrypEffect {effect: Effect::Reflect, duration: 2, meta: None, tick: None }],
Skill::ReflectIII => vec![CrypEffect {effect: Effect::Reflect, duration: 3, meta: None, tick: None }],
Skill::ThrowI => vec![CrypEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None},
CrypEffect {effect: Effect::Vulnerable, duration: 3, meta: Some(EffectMeta::Multiplier(150)), tick: None}],
Skill::ThrowII => vec![CrypEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None},
CrypEffect {effect: Effect::Vulnerable, duration: 4, meta: Some(EffectMeta::Multiplier(200)), tick: None}],
Skill::ThrowIII => vec![CrypEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None},
CrypEffect {effect: Effect::Vulnerable, duration: 4, meta: Some(EffectMeta::Multiplier(250)), tick: None}],
Skill::RuinI => vec![CrypEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}],
Skill::RuinII => vec![CrypEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}],
Skill::RuinIII => vec![CrypEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::ScatterI => vec![CrypEffect {effect: Effect::Scatter, duration: 2, meta: None, tick: None}],
Skill::ScatterII => vec![CrypEffect {effect: Effect::Scatter, duration: 3, meta: None, tick: None}],
Skill::ScatterIII => vec![CrypEffect {effect: Effect::Scatter, duration: 4, meta: None, tick: None}],
Skill::SilenceI => vec![CrypEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}],
Skill::SilenceII => vec![CrypEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}],
Skill::SilenceIII => vec![CrypEffect {effect: Effect::Silence, duration: 3, meta: None, tick: None}],
Skill::Siphon => vec![CrypEffect {effect: Effect::Siphon, duration: 2, meta: None, tick: None}],
Skill::SleepI => vec![CrypEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::SleepII => vec![CrypEffect {effect: Effect::Stun, duration: 3, meta: None, tick: None}],
Skill::SleepIII => vec![CrypEffect {effect: Effect::Stun, duration: 4, meta: None, tick: None}],
Skill::SnareI => vec![CrypEffect {effect: Effect::Snare, duration: 2, meta: None, tick: None}],
Skill::SnareII => vec![CrypEffect {effect: Effect::Snare, duration: 3, meta: None, tick: None}],
Skill::SnareIII => vec![CrypEffect {effect: Effect::Snare, duration: 4, meta: None, tick: None}],
Skill::Strangle => vec![CrypEffect {effect: Effect::Strangle, duration: 2, meta: None, tick: None}],
Skill::Stun => vec![CrypEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::TauntI => vec![CrypEffect {effect: Effect::Taunt, duration: 2, meta: None, tick: None}],
Skill::TauntII => vec![CrypEffect {effect: Effect::Taunt, duration: 3, meta: None, tick: None}],
Skill::TauntIII => vec![CrypEffect {effect: Effect::Taunt, duration: 4, meta: None, tick: None}],
Skill::Triage => vec![CrypEffect {effect: Effect::Triage, duration: 2, meta: None, tick: None}],
//Unused
Skill::Injure => vec![CrypEffect {effect: Effect::Injured, duration: 2, meta: None, tick: None }],
_ => {
panic!("{:?} no skill effect", self);
},
}
}
pub fn base_cd(&self) -> Cooldown {
match self {
Skill::Attack => None,
Skill::Debuff => Some(1),
Skill::Buff => None,
Skill::StrikeI => None,
Skill::StrikeII => None,
Skill::StrikeIII => None,
Skill::Block => None, // reduce damage
Skill::Parry => Some(2), // avoid all damage
Skill::Riposte => None, // used on parry
Skill::SnareI => Some(2),
Skill::SnareII => Some(2),
Skill::SnareIII => Some(2),
Skill::Stun => Some(2),
Skill::HealI => None,
Skill::HealII => None,
Skill::HealIII => None,
Skill::Triage => None, // hot
Skill::TriageTick => None,
Skill::ThrowI => Some(1), // no damage stun, adds vulnerable
Skill::ThrowII => Some(1),
Skill::ThrowIII => Some(1),
Skill::BlastI => None,
Skill::BlastII => None,
Skill::BlastIII => None,
Skill::ChaosI => None,
Skill::ChaosII => None,
Skill::ChaosIII => None,
Skill::AmplifyI => Some(1),
Skill::AmplifyII => Some(1),
Skill::AmplifyIII => Some(1),
Skill::Impurity => Some(3),
Skill::ImpureBlast => None,
Skill::InvertI => Some(2),
Skill::InvertII => Some(2),
Skill::InvertIII => Some(2),
Skill::Decay => Some(1), // dot
Skill::DecayTick => None,
Skill::Siphon => None,
Skill::SiphonTick => None,
Skill::CurseI => Some(1),
Skill::CurseII => Some(1),
Skill::CurseIII => Some(1),
Skill::ScatterI => Some(2),
Skill::ScatterII => Some(2),
Skill::ScatterIII => Some(2),
Skill::SilenceI => Some(3),
Skill::SilenceII => Some(2),
Skill::SilenceIII => Some(2),
Skill::PurifyI => None,
Skill::PurifyII => None,
Skill::PurifyIII => None,
Skill::PurgeI => None,
Skill::PurgeII => None,
Skill::PurgeIII => None,
Skill::Banish => Some(1),
Skill::HexI => Some(1),
Skill::HexII => Some(2),
Skill::HexIII => Some(2),
Skill::Haste => Some(2),
Skill::HasteStrike => None, // Used in haste
Skill::ReflectI => Some(2),
Skill::ReflectII => Some(2),
Skill::ReflectIII => Some(2),
Skill::RechargeI => Some(2),
Skill::RechargeII => Some(2),
Skill::RechargeIII => Some(2),
Skill::RuinI => Some(3),
Skill::RuinII => Some(2),
Skill::RuinIII => Some(2),
Skill::SlayI => None,
Skill::SlayII => None,
Skill::SlayIII => None,
Skill::SleepI => Some(3),
Skill::SleepII => Some(3),
Skill::SleepIII => Some(3),
Skill::Strangle => Some(2),
Skill::StrangleTick => None,
Skill::ClutchI => Some(1),
Skill::ClutchII => Some(2),
Skill::ClutchIII => Some(3),
Skill::TauntI => Some(2),
Skill::TauntII => Some(2),
Skill::TauntIII => Some(2),
Skill::Injure => Some(2),
Skill::Corrupt => Some(1),
Skill::CorruptionTick => None,
Skill::Hostility => Some(1),
// -----------------
// Test
// -----------------
Skill::TestAttack => None,
Skill::TestHeal => None,
Skill::TestTouch => None,
Skill::TestStun => None,
Skill::TestBlock => None,
Skill::TestSiphon => None,
Skill::TestParry => None,
}
}
pub fn ko_castable(&self) -> bool {
match self {
Skill::TriageTick => true,
Skill::DecayTick => true,
Skill::SiphonTick => true,
Skill::CorruptionTick => true,
_ => false,
}
}
pub fn is_tick(&self) -> bool {
match self {
Skill::CorruptionTick => true,
Skill::DecayTick => true,
Skill::SiphonTick => true,
Skill::StrangleTick => true,
Skill::TriageTick => 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::StrikeI => u8::max_value(),
Skill::StrikeII => Skill::StrikeI.speed(),
Skill::StrikeIII => Skill::StrikeI.speed(),
Skill::SiphonTick => Skill::Siphon.speed(),
Skill::DecayTick => Skill::Decay.speed(),
Skill::TriageTick => Skill::Triage.speed(),
Skill::StrangleTick => Skill::Strangle.speed(),
Skill::CorruptionTick => Skill::Corrupt.speed(),
_ => Item::from(*self).speed(),
}
}
pub fn aoe(&self) -> bool {
match self {
Skill::RuinI |
Skill::RuinII |
Skill::RuinIII => true,
_ => false,
}
}
pub fn self_targeting(&self) -> bool {
match self {
Skill::Block |
Skill::Parry |
Skill::ClutchI |
Skill::ClutchII |
Skill::ClutchIII |
Skill::Corrupt |
Skill::TestBlock |
Skill::TestParry => true,
_ => false,
}
}
pub fn defensive(&self) -> bool {
let mut rng = thread_rng();
match self {
Skill::HealI |
Skill::HealII |
Skill::HealIII |
Skill::Triage |
Skill::PurifyI |
Skill::PurifyII |
Skill::PurifyIII |
Skill::Parry |
Skill::ClutchI |
Skill::ClutchII |
Skill::ClutchIII |
Skill::ScatterI |
Skill::ScatterII |
Skill::ScatterIII |
Skill::RechargeI |
Skill::RechargeII |
Skill::RechargeIII |
Skill::ReflectI |
Skill::ReflectII |
Skill::ReflectIII |
Skill::Haste |
Skill::InvertI |
Skill::InvertII |
Skill::InvertIII |
Skill::AmplifyI |
Skill::AmplifyII |
Skill::AmplifyIII |
Skill::Hostility |
Skill::Corrupt |
Skill::Block => true,
Skill::Banish => rng.gen_bool(0.5),
_ => false,
}
}
fn components(&self) -> Vec<Item> {
let mut components = Item::from(*self).components();
components.sort_unstable();
return components;
}
fn colours(&self) -> Vec<Colour> {
let mut components = self.components();
let colour_items = [Item::Red, Item::Green, Item::Blue];
components.dedup();
return components.iter()
.filter(|i| colour_items.contains(i))
.map(|i| i.into_colour())
.collect::<Vec<Colour>>();
}
fn base(&self) -> Skill {
let bases = [Item::Attack, Item::Stun, Item::Buff, Item::Debuff, Item::Block];
match self.components()
.iter()
.find(|i| bases.contains(i)) {
Some(i) => i.into_skill().unwrap(),
None => panic!("{:?} has no base item", self),
}
}
}
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)));
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn stun(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn sleep(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
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 clutch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn taunt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let red_amount = source.red_damage().pct(skill.multiplier());
results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, 0)));
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn throw(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn strangle(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter().for_each(|e| {
let CrypEffect { effect: _, duration, meta: _, tick: _ } = e;
let strangle = e.clone().set_tick(Cast::new_tick(source, target, Skill::StrangleTick));
results.push(Resolution::new(source, target).event(target.add_effect(skill, strangle)));
let attacker_strangle = CrypEffect::new(Effect::Strangling, duration);
results.push(Resolution::new(source, source).event(source.add_effect(skill, attacker_strangle)));
});
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 {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn buff(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn parry(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let red_amount = source.red_damage().pct(skill.multiplier());
results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, 0)));
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
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 {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
let s_multi = target.skills
.iter()
.fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Red) {
true => acc + 35,
false => acc,
});
let amount = source.red_damage().pct(skill.multiplier()).pct(s_multi);
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
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 {
skill.effect().into_iter().for_each(|e| {
let triage = e.clone().set_tick(Cast::new_tick(source, target, Skill::TriageTick));
results.push(Resolution::new(source, target).event(target.add_effect(skill, triage)));
});
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, 30);
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, 30);
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 {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn haste(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn debuff(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn decay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter().for_each(|e| {
let CrypEffect { effect, duration: _, meta: _, tick: _ } = e;
let apply_effect = match effect {
Effect::Wither => e.clone(),
Effect::Decay => e.clone().set_tick(Cast::new_tick(source, target, Skill::DecayTick)),
_ => panic!("wrong decay effects"),
};
results.push(Resolution::new(source, target).event(target.add_effect(skill, apply_effect)));
});
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 corrupt = skill.effect().first().unwrap().clone();
results.push(Resolution::new(source, target).event(target.add_effect(skill, corrupt)));
return results;;
}
fn corruption(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let corruption = skill.effect().last().unwrap().clone()
.set_tick(Cast::new_tick(source, target, Skill::CorruptionTick));
results.push(Resolution::new(source, target).event(target.add_effect(skill, corruption)));
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 {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn hex(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn hostility(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let hostility = skill.effect().first().unwrap().clone();
results.push(Resolution::new(source, target).event(target.add_effect(skill, hostility)));
return results;;
}
fn hatred(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions {
let hatred = skill.effect().last().unwrap().clone()
.set_meta(EffectMeta::AddedDamage(amount));
results.push(Resolution::new(source, target).event(target.add_effect(reflect_skill, hatred)));
return results;;
}
fn curse(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn impurity(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn invert(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;;
}
fn reflect(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
let blue_amount = source.blue_damage().pct(skill.multiplier());
results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount)));
return results;;
}
fn recharge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let red_amount = source.red_damage().pct(skill.multiplier());
let blue_amount = source.blue_damage().pct(skill.multiplier());
results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, blue_amount)));
return results;
}
fn siphon(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter().for_each(|e| {
let siphon = e.clone().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 scatter(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
let blue_amount = source.blue_damage().pct(skill.multiplier());
results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount)));
skill.effect().into_iter().for_each(|e| {
let scatter = e.clone().set_meta(EffectMeta::ScatterTarget(target.id));
results.push(Resolution::new(source, target).event(source.add_effect(skill, scatter)));
});
return results;
}
fn scatter_hit(source: &Cryp, target: &Cryp, mut results: Resolutions, game: &mut Game, event: Event) -> Resolutions {
match event {
Event::Damage { amount, skill, mitigation: _, colour } => {
let scatter = target.effects.iter().find(|e| e.effect == Effect::Scatter).unwrap();
if let Some(EffectMeta::ScatterTarget(scatter_target_id)) = scatter.meta {
let mut scatter_target = game.cryp_by_id(scatter_target_id).unwrap();
let res = match colour {
Colour::Red => scatter_target.deal_red_damage(skill, amount),
Colour::Blue => scatter_target.deal_blue_damage(skill, amount),
Colour::Green => scatter_target.deal_green_damage(skill, amount),
};
results.push(Resolution::new(target, scatter_target).event(Event::Skill { skill: Skill::ScatterI }));
res.into_iter().for_each(|e| results.push(Resolution::new(&source, &scatter_target).event(e)));
} else {
panic!("not a scatter target {:?}", scatter);
}
return results;
},
_ => panic!("{:?} scatter hit not damage event", event),
}
}
fn silence(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
let s_multi = target.skills
.iter()
.fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Blue) {
true => acc + 45,
false => acc,
});
let amount = source.blue_damage().pct(skill.multiplier()).pct(s_multi);
target.deal_blue_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
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| ce.effect.category() == EffectCategory::Buff) {
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| ce.effect.category() == EffectCategory::Debuff) {
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 {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
#[cfg(test)]
mod tests {
use skill::*;
#[test]
fn heal_test() {
let mut x = Cryp::new()
.named(&"muji".to_string())
.learn(Skill::HealI);
let mut y = Cryp::new()
.named(&"camel".to_string())
.learn(Skill::HealI);
x.deal_red_damage(Skill::Attack, 5);
heal(&mut y, &mut x, vec![], Skill::HealI);
}
#[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::TestAttack);
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::ClutchI);
assert!(y.affected(Effect::Clutch));
let mut results = hex(&mut x, &mut y, vec![], Skill::HexI);
let Resolution { source: _, target: _, event } = results.remove(0);
match event {
Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Clutch)),
_ => panic!("not immune cluthc"),
};
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::HealI).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);
invert(&mut y.clone(), &mut y, vec![], Skill::InvertI);
assert!(y.affected(Effect::Invert));
// heal should deal green damage
heal(&mut x, &mut y, vec![], Skill::TestHeal);
assert!(y.green_life() == 768);
// attack should heal and recharge red shield
let mut results = attack(&mut x, &mut y, vec![], Skill::TestAttack);
assert!(y.green_life() == 1024);
match results.remove(0).event {
Event::Inversion { skill } => assert_eq!(skill, Skill::TestAttack),
_ => panic!("not inversion"),
};
match results.remove(0).event {
Event::Healing { skill: _, overhealing: _, amount } => assert_eq!(amount, 256),
_ => panic!("not healing from inversion"),
};
match results.remove(0).event {
Event::Recharge { skill: _, red, blue: _ } => assert_eq!(red, 64),
_ => 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::ReflectI);
assert!(y.affected(Effect::Reflect));
let mut results = vec![];
results = resolve(Skill::TestAttack, &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::TestAttack),
_ => 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 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::BlastI, 5);
let mut results = recharge(&mut x, &mut y, vec![], Skill::RechargeI);
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::SilenceI);
assert!(x.effects.iter().any(|e| e.effect == Effect::Silence));
assert!(x.disabled(Skill::SilenceI).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::AmplifyI);
assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify));
assert_eq!(x.blue_damage(), 75);
}
#[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::PurifyI);
assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay));
}
}