mnml/server/src/skill.rs
2019-10-31 15:41:20 +10:00

2214 lines
82 KiB
Rust

use rand::{thread_rng, Rng};
use uuid::Uuid;
use util::{IntPct};
use construct::{Construct, ConstructEffect, EffectMeta};
use item::{Item};
use game::{Game};
use effect::{Effect, Colour, Cooldown};
pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions {
let mut resolutions = vec![];
let mut a = Construct::new();
a.id = a_id;
let mut b = Construct::new();
b.id = b_id;
if skill.aoe() { // Send an aoe skill event for anims
resolutions.push(Resolution::new(&a, &b).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd));
}
return resolve(skill, &mut a, &mut b, resolutions);
}
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.construct_by_id(cast.source_construct_id).unwrap().clone();
let targets = game.get_targets(cast.skill, &source, cast.target_construct_id);
if skill.aoe() { // Send an aoe skill event for anims
resolutions.push(Resolution::new(&source,
&game.construct_by_id(cast.target_construct_id).unwrap().clone()).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd));
}
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.construct_by_id(cast.source_construct_id).unwrap().clone();
let mut target = game.construct_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_construct(&mut source);
game.update_construct(&mut target);
// do additional steps
resolutions = post_resolve(cast.skill, game, resolutions);
}
return resolutions;
}
pub fn resolve(skill: Skill, source: &mut Construct, target: &mut Construct, mut resolutions: Vec<Resolution>) -> Resolutions {
if let Some(_disable) = source.disabled(skill) {
// resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly));
return resolutions;
}
if target.is_ko() {
// resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }).stages(EventStages::PostOnly));
return resolutions;
}
if target.affected(Effect::Reflect) && skill.colours().contains(&Colour::Blue) && !skill.is_tick() {
// guard against overflow
if source.affected(Effect::Reflect) {
return resolutions;
}
resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill }));
return resolve(skill, &mut source.clone(), source, resolutions);
}
if source.affected(Effect::Haste) {
match skill {
Skill::Attack |
Skill::Slay|
Skill::Chaos|
Skill::Strike=> {
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::Hybrid) {
match skill {
Skill::Blast|
Skill::Chaos|
Skill::Siphon=> {
let amount = source.green_power().pct(Skill::HybridBlast.multiplier());
target.deal_blue_damage(Skill::HybridBlast, 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::Amplify|
Skill::AmplifyPlus |
Skill::AmplifyPlusPlus => amplify(source, target, resolutions, skill),
Skill::Banish|
Skill::BanishPlus |
Skill::BanishPlusPlus => banish(source, target, resolutions, skill),
Skill::Bash|
Skill::BashPlus |
Skill::BashPlusPlus => bash(source, target, resolutions, skill),
Skill::Blast|
Skill::BlastPlus |
Skill::BlastPlusPlus => blast(source, target, resolutions, skill),
Skill::Chaos|
Skill::ChaosPlus |
Skill::ChaosPlusPlus => chaos(source, target, resolutions, skill),
Skill::Sustain|
Skill::SustainPlus |
Skill::SustainPlusPlus => sustain(source, target, resolutions, skill),
Skill::Electrify|
Skill::ElectrifyPlus |
Skill::ElectrifyPlusPlus => electrify(source, target, resolutions, skill),
Skill::ElectrocuteTick|
Skill::ElectrocuteTickPlus |
Skill::ElectrocuteTickPlusPlus => electrocute_tick(source, target, resolutions, skill),
Skill::Curse|
Skill::CursePlus |
Skill::CursePlusPlus => curse(source, target, resolutions, skill),
Skill::Decay|
Skill::DecayPlus |
Skill::DecayPlusPlus => decay(source, target, resolutions, skill), // dot
Skill::DecayTick|
Skill::DecayTickPlus |
Skill::DecayTickPlusPlus => decay_tick(source, target, resolutions, skill), // dot
Skill::Haste|
Skill::HastePlus |
Skill::HastePlusPlus => haste(source, target, resolutions, skill), // speed slow
Skill::Heal|
Skill::HealPlus |
Skill::HealPlusPlus => heal(source, target, resolutions, skill),
Skill::Absorb|
Skill::AbsorbPlus |
Skill::AbsorbPlusPlus => absorb(source, target, resolutions, skill),
Skill::Hybrid|
Skill::HybridPlus |
Skill::HybridPlusPlus => hybrid(source, target, resolutions, skill),
Skill::Invert|
Skill::InvertPlus |
Skill::InvertPlusPlus => invert(source, target, resolutions, skill),
Skill::Counter|
Skill::CounterPlus |
Skill::CounterPlusPlus => counter(source, target, resolutions, skill),
Skill::Purge|
Skill::PurgePlus |
Skill::PurgePlusPlus => purge(source, target, resolutions, skill), // dispel all buffs
Skill::Purify|
Skill::PurifyPlus |
Skill::PurifyPlusPlus => purify(source, target, resolutions, skill),
Skill::Recharge|
Skill::RechargePlus |
Skill::RechargePlusPlus => recharge(source, target, resolutions, skill),
Skill::Reflect|
Skill::ReflectPlus |
Skill::ReflectPlusPlus => reflect(source, target, resolutions, skill),
Skill::Ruin|
Skill::RuinPlus |
Skill::RuinPlusPlus => ruin(source, target, resolutions, skill),
Skill::Link|
Skill::LinkPlus |
Skill::LinkPlusPlus => link(source, target, resolutions, skill), // target is immune to magic damage and fx
Skill::Silence|
Skill::SilencePlus |
Skill::SilencePlusPlus => silence(source, target, resolutions, skill), // target cannot cast spells
Skill::Siphon|
Skill::SiphonPlus |
Skill::SiphonPlusPlus => siphon(source, target, resolutions, skill), // dot
Skill::SiphonTick|
Skill::SiphonTickPlus |
Skill::SiphonTickPlusPlus => siphon_tick(source, target, resolutions, skill), // dot
Skill::Slay|
Skill::SlayPlus |
Skill::SlayPlusPlus => slay(source, target, resolutions, skill), // hybrid dmg self heal
Skill::Sleep|
Skill::SleepPlus |
Skill::SleepPlusPlus => sleep(source, target, resolutions, skill), // heal stun
Skill::Restrict|
Skill::RestrictPlus |
Skill::RestrictPlusPlus => restrict(source, target, resolutions, skill),
Skill::Strike|
Skill::StrikePlus |
Skill::StrikePlusPlus => strike(source, target, resolutions, skill),
Skill::Intercept|
Skill::InterceptPlus |
Skill::InterceptPlusPlus => intercept(source, target, resolutions, skill),
Skill::Break|
Skill::BreakPlus |
Skill::BreakPlusPlus => break_(source, target, resolutions, skill), // no damage stun, adds vulnerable
Skill::Triage|
Skill::TriagePlus |
Skill::TriagePlusPlus => triage(source, target, resolutions, skill), // hot
Skill::TriageTick|
Skill::TriageTickPlus |
Skill::TriageTickPlusPlus => 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::Electrocute |
Skill::ElectrocutePlus |
Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"),
Skill::HasteStrike => panic!("should only trigger from haste"),
Skill::Absorption|
Skill::AbsorptionPlus |
Skill::AbsorptionPlusPlus => panic!("should only trigger from absorb"),
Skill::HybridBlast => panic!("should only trigger from hybrid"),
Skill::CounterAttack|
Skill::CounterAttackPlus |
Skill::CounterAttackPlusPlus => panic!("should only trigger from counter"),
// Not used
};
return resolutions;
}
fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions {
for Resolution { source, target, event, stages: _ } in resolutions.clone() {
let mut source = game.construct_by_id(source.id).unwrap().clone();
let mut target = game.construct_by_id(target.id).unwrap().clone();
match event {
Event::Damage { amount, skill, mitigation: _, colour: c } => {
if target.affected(Effect::Electric) && !skill.is_tick() {
let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter()
.find(|e| e.effect == Effect::Electric).unwrap().clone();
match meta {
Some(EffectMeta::Skill(s)) => {
// Gurad against reflect overflow
if !(source.affected(Effect::Reflect) && target.affected(Effect::Reflect)) {
// Check reflect don't bother if electrocute is procing on death
if source.affected(Effect::Reflect) && !target.is_ko() {
resolutions.push(Resolution::new(&target, &source)
.event(Event::Reflection { skill: s }).stages(EventStages::EndPost));
resolutions = electrocute(&mut source, &mut target, resolutions, s);
} else {
resolutions = electrocute(&mut target, &mut source, resolutions, s);
}
}
},
_ => panic!("no electrify skill"),
};
}
if target.affected(Effect::Absorb) && !target.is_ko() {
let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter()
.find(|e| e.effect == Effect::Absorb).unwrap().clone();
match meta {
Some(EffectMeta::Skill(s)) => {
resolutions = absorption(&mut source, &mut target, resolutions, skill, amount, s);
},
_ => panic!("no absorb skill"),
};
}
if c == Colour::Red {
if target.affected(Effect::Counter) && !target.is_ko() {
let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter()
.find(|e| e.effect == Effect::Counter).unwrap().clone();
match meta {
Some(EffectMeta::Skill(s)) => {
resolutions = counter_attack(&mut target, &mut source, resolutions, s);
},
_ => panic!("no counter skill"),
};
}
}
},
_ => (),
};
if target.is_ko() {
// resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly));
target.effects.clear();
}
game.update_construct(&mut source);
game.update_construct(&mut target);
};
return resolutions;
}
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub struct Cast {
pub id: Uuid,
pub source_player_id: Uuid,
pub source_construct_id: Uuid,
pub target_construct_id: Uuid,
pub skill: Skill,
pub speed: u64,
}
impl Cast {
pub fn new(source_construct_id: Uuid, source_player_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Cast {
return Cast {
id: Uuid::new_v4(),
source_construct_id,
source_player_id,
target_construct_id,
skill,
speed: 0,
};
}
pub fn new_tick(source: &mut Construct, target: &mut Construct, skill: Skill) -> Cast {
Cast {
id: Uuid::new_v4(),
source_construct_id: source.id,
source_player_id: source.account,
target_construct_id: target.id,
skill,
speed: source.skill_speed(skill),
}
}
pub fn used_cooldown(&self) -> bool {
return self.skill.base_cd().is_some();
}
}
pub type Disable = Vec<Effect>;
pub type Immunity = Vec<Effect>;
// used to show the progress of a construct
// while the resolutions are animating
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct EventConstruct {
pub id: Uuid,
pub red: u64,
pub green: u64,
pub blue: u64,
}
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub enum EventStages {
#[serde(rename = "START_SKILL END_SKILL POST_SKILL")]
AllStages, // Anim Anim Anim
#[serde(rename = "START_SKILL END_SKILL")]
StartEnd, // Anim Anim Skip
#[serde(rename = "START_SKILL POST_SKILL")]
StartPost, // Anim Skip Anim
#[serde(rename = "START_SKILL")]
StartOnly, // Anim Skip Skip
#[serde(rename = "END_SKILL POST_SKILL")]
EndPost, // Skip Anim Anim
#[serde(rename = "END_SKILL")]
EndOnly, // Skip Anim Skip
#[serde(rename = "POST_SKILL")]
PostOnly, // Skip Skip Anim
#[serde(rename = "")]
NoStages, // Skip Skip Skip
}
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Resolution {
pub source: EventConstruct,
pub target: EventConstruct,
pub event: Event,
pub stages: EventStages,
}
impl Resolution {
fn new(source: &Construct, target: &Construct) -> Resolution {
Resolution {
source: EventConstruct {
id: source.id,
red: source.red_life(),
green: source.green_life(),
blue: source.blue_life(),
},
target: EventConstruct {
id: target.id,
red: target.red_life(),
green: target.green_life(),
blue: target.blue_life(),
},
event: Event::Incomplete,
stages: EventStages::AllStages,
}
}
fn event(mut self, e: Event) -> Resolution {
self.event = e;
self
}
fn stages(mut self, s: EventStages) -> Resolution {
self.stages = s;
self
}
pub fn get_delay(self) -> i64 {
let source_duration = 1000; // Time for SOURCE ONLY
let target_delay = 500; // Used for Source + Target
let target_duration = 1500; // Time for TARGET ONLY
let post_skill = 1000; // Time for all POST
let source_and_target_total = target_delay + target_duration; // SOURCE + TARGET time
match self.stages {
EventStages::AllStages => source_and_target_total + post_skill, // Anim Anim Anim
EventStages::StartEnd => source_and_target_total, // Anim Anim Skip
EventStages::StartPost => source_duration + post_skill, // Anim Skip Anim
EventStages::StartOnly => source_duration, // Anim Skip Skip
EventStages::EndPost => target_duration + post_skill, // Skip Anim Anim
EventStages::EndOnly => target_duration, // Skip Anim Skip
EventStages::PostOnly => post_skill, // Skip Skip Anim
EventStages::NoStages => 0, // Skip Skip Skip
}
}
}
#[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 },
AoeSkill { skill: Skill },
Skill { skill: Skill },
Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec<ConstructEffect> },
Removal { effect: Effect, construct_effects: Vec<ConstructEffect> },
TargetKo { skill: Skill },
// skill not necessary but makes it neater as all events are arrays in js
Ko (),
Forfeit (),
Incomplete,
// not used
Evasion { skill: Skill, evasion_rating: u64 },
}
pub type Resolutions = Vec<Resolution>;
#[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,
Amplify,
#[serde(rename = "Amplify+")]
AmplifyPlus,
#[serde(rename = "Amplify++")]
AmplifyPlusPlus,
Absorb,
#[serde(rename = "Absorb+")]
AbsorbPlus,
#[serde(rename = "Absorb++")]
AbsorbPlusPlus,
Banish,
#[serde(rename = "Banish+")]
BanishPlus,
#[serde(rename = "Banish++")]
BanishPlusPlus,
Bash,
#[serde(rename = "Bash+")]
BashPlus,
#[serde(rename = "Bash++")]
BashPlusPlus,
Blast,
#[serde(rename = "Blast+")]
BlastPlus,
#[serde(rename = "Blast++")]
BlastPlusPlus,
Chaos,
#[serde(rename = "Chaos+")]
ChaosPlus,
#[serde(rename = "Chaos++")]
ChaosPlusPlus,
Sustain,
#[serde(rename = "Sustain+")]
SustainPlus,
#[serde(rename = "Sustain++")]
SustainPlusPlus,
Electrify,
#[serde(rename = "Electrify+")]
ElectrifyPlus,
#[serde(rename = "Electrify++")]
ElectrifyPlusPlus,
Curse,
#[serde(rename = "Curse+")]
CursePlus,
#[serde(rename = "Curse++")]
CursePlusPlus,
Decay,
#[serde(rename = "Decay+")]
DecayPlus,
#[serde(rename = "Decay++")]
DecayPlusPlus,
Haste,
#[serde(rename = "Haste+")]
HastePlus,
#[serde(rename = "Haste++")]
HastePlusPlus,
Heal,
#[serde(rename = "Heal+")]
HealPlus,
#[serde(rename = "Heal++")]
HealPlusPlus,
Hybrid,
#[serde(rename = "Hybrid+")]
HybridPlus,
#[serde(rename = "Hybrid++")]
HybridPlusPlus,
Invert,
#[serde(rename = "Invert+")]
InvertPlus,
#[serde(rename = "Invert++")]
InvertPlusPlus,
Counter,
#[serde(rename = "Counter+")]
CounterPlus,
#[serde(rename = "Counter++")]
CounterPlusPlus,
Purge,
#[serde(rename = "Purge+")]
PurgePlus,
#[serde(rename = "Purge++")]
PurgePlusPlus,
Purify,
#[serde(rename = "Purify+")]
PurifyPlus,
#[serde(rename = "Purify++")]
PurifyPlusPlus,
Reflect,
#[serde(rename = "Reflect+")]
ReflectPlus,
#[serde(rename = "Reflect++")]
ReflectPlusPlus,
Recharge,
#[serde(rename = "Recharge+")]
RechargePlus,
#[serde(rename = "Recharge++")]
RechargePlusPlus,
Ruin,
#[serde(rename = "Ruin+")]
RuinPlus,
#[serde(rename = "Ruin++")]
RuinPlusPlus,
Link,
#[serde(rename = "Link+")]
LinkPlus,
#[serde(rename = "Link++")]
LinkPlusPlus,
Silence,
#[serde(rename = "Silence+")]
SilencePlus,
#[serde(rename = "Silence++")]
SilencePlusPlus,
Slay,
#[serde(rename = "Slay+")]
SlayPlus,
#[serde(rename = "Slay++")]
SlayPlusPlus,
Sleep,
#[serde(rename = "Sleep+")]
SleepPlus,
#[serde(rename = "Sleep++")]
SleepPlusPlus,
Restrict,
#[serde(rename = "Restrict+")]
RestrictPlus,
#[serde(rename = "Restrict++")]
RestrictPlusPlus,
Strike,
#[serde(rename = "Strike+")]
StrikePlus,
#[serde(rename = "Strike++")]
StrikePlusPlus,
Siphon,
#[serde(rename = "Siphon+")]
SiphonPlus,
#[serde(rename = "Siphon++")]
SiphonPlusPlus,
Intercept,
#[serde(rename = "Intercept+")]
InterceptPlus,
#[serde(rename = "Intercept++")]
InterceptPlusPlus,
Break,
#[serde(rename = "Break+")]
BreakPlus,
#[serde(rename = "Break++")]
BreakPlusPlus,
Triage,
#[serde(rename = "Triage+")]
TriagePlus,
#[serde(rename = "Triage++")]
TriagePlusPlus,
Absorption,
#[serde(rename = "Absorption+")]
AbsorptionPlus,
#[serde(rename = "Absorption++")]
AbsorptionPlusPlus,
CounterAttack,
#[serde(rename = "CounterAttack+")]
CounterAttackPlus,
#[serde(rename = "CounterAttack++")]
CounterAttackPlusPlus,
Electrocute,
#[serde(rename = "Electrocute+")]
ElectrocutePlus,
#[serde(rename = "Electrocute++")]
ElectrocutePlusPlus,
ElectrocuteTick,
#[serde(rename = "ElectrocuteTick+")]
ElectrocuteTickPlus,
#[serde(rename = "ElectrocuteTick++")]
ElectrocuteTickPlusPlus,
DecayTick, // dot
#[serde(rename = "DecayTick+")]
DecayTickPlus,
#[serde(rename = "DecayTick++")]
DecayTickPlusPlus,
HasteStrike,
HybridBlast,
SiphonTick,
#[serde(rename = "SiphonTick+")]
SiphonTickPlus,
#[serde(rename = "SiphonTick++")]
SiphonTickPlusPlus,
TriageTick,
#[serde(rename = "TriageTick+")]
TriageTickPlus,
#[serde(rename = "TriageTick++")]
TriageTickPlusPlus,
}
impl Skill {
pub fn multiplier(&self) -> u64 {
match self {
// Attack Base
Skill::Attack => 80, // Base
Skill::Blast=> 105, // BB
Skill::BlastPlus => 140, // BB
Skill::BlastPlusPlus => 200, // BB
Skill::Chaos=> 40, // BR
Skill::ChaosPlus => 65, // BR
Skill::ChaosPlusPlus => 90, // BR
Skill::Heal=> 125, //GG
Skill::HealPlus => 185, //GG
Skill::HealPlusPlus => 270, //GG
Skill::SiphonTick=> 25, // GB
Skill::SiphonTickPlus => 30,
Skill::SiphonTickPlusPlus => 40,
Skill::Slay=> 45, // RG
Skill::SlayPlus => 65,
Skill::SlayPlusPlus => 100,
Skill::Strike=> 90, //RR
Skill::StrikePlus => 140,
Skill::StrikePlusPlus => 200,
// Block Base
Skill::ElectrocuteTick=> 80,
Skill::ElectrocuteTickPlus => 100,
Skill::ElectrocuteTickPlusPlus => 130,
Skill::CounterAttack=> 120,
Skill::CounterAttackPlus => 160,
Skill::CounterAttackPlusPlus => 230,
Skill::Purify=> 45, //Green dmg (heal)
Skill::PurifyPlus => 70,
Skill::PurifyPlusPlus => 105,
Skill::Reflect=> 45, //Recharge blue life (heal)
Skill::ReflectPlus => 70,
Skill::ReflectPlusPlus => 100,
Skill::Recharge=> 70, //Recharge red and blue life (heal)
Skill::RechargePlus => 110,
Skill::RechargePlusPlus => 170,
Skill::Sustain => 120, // Recharge red life (heal)
Skill::SustainPlus => 150,
Skill::SustainPlusPlus => 230,
// Stun Base
Skill::Sleep=> 200, //Green dmg (heal)
Skill::SleepPlus => 290,
Skill::SleepPlusPlus => 400,
Skill::Banish=> 40, //Green dmg (heal)
Skill::BanishPlus => 75,
Skill::BanishPlusPlus => 125,
Skill::Bash=> 45,
Skill::BashPlus => 65,
Skill::BashPlusPlus => 100,
Skill::Link=> 75,
Skill::LinkPlus => 100,
Skill::LinkPlusPlus => 150,
// Debuff Base
Skill::DecayTick=> 33,
Skill::DecayTickPlus => 45,
Skill::DecayTickPlusPlus => 70,
Skill::Silence=> 55, // Deals more per blue skill on target
Skill::SilencePlus => 80,
Skill::SilencePlusPlus => 110,
Skill::Restrict=> 40, // Deals more per red skill on target
Skill::RestrictPlus => 65,
Skill::RestrictPlusPlus => 100,
// Buff base
Skill::HybridBlast => 50,
Skill::HasteStrike => 60,
Skill::Intercept=> 80,
Skill::InterceptPlus => 110,
Skill::InterceptPlusPlus => 150,
Skill::TriageTick=> 75,
Skill::TriageTickPlus => 110,
Skill::TriageTickPlusPlus => 140,
_ => 100,
}
}
pub fn effect(&self) -> Vec<ConstructEffect> {
match self {
// Modifiers
Skill::Amplify => vec![ConstructEffect {effect: Effect::Amplify, duration: 2,
meta: Some(EffectMeta::Multiplier(150)), tick: None}],
Skill::AmplifyPlus => vec![ConstructEffect {effect: Effect::Amplify, duration: 3,
meta: Some(EffectMeta::Multiplier(175)), tick: None}],
Skill::AmplifyPlusPlus => vec![ConstructEffect {effect: Effect::Amplify, duration: 4,
meta: Some(EffectMeta::Multiplier(200)), tick: None}],
Skill::Banish => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}],
Skill::BanishPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}],
Skill::BanishPlusPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}],
Skill::Block => vec![ConstructEffect {effect: Effect::Block, duration: 1,
meta: Some(EffectMeta::Multiplier(35)), tick: None}],
Skill::Buff => vec![ConstructEffect {effect: Effect::Buff, duration: 3,
meta: Some(EffectMeta::Multiplier(130)), tick: None }],
Skill::Electrify => vec![ConstructEffect {effect: Effect::Electric, duration: 1,
meta: Some(EffectMeta::Skill(Skill::Electrocute)), tick: None}],
Skill::ElectrifyPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 1,
meta: Some(EffectMeta::Skill(Skill::ElectrocutePlus)), tick: None}],
Skill::ElectrifyPlusPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 1,
meta: Some(EffectMeta::Skill(Skill::ElectrocutePlusPlus)), tick: None}],
Skill::Electrocute => vec![ConstructEffect {effect: Effect::Electrocute, duration: 2,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteTick)), tick: None}],
Skill::ElectrocutePlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 3,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickPlus)), tick: None}],
Skill::ElectrocutePlusPlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 4,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickPlusPlus)), tick: None}],
Skill::Sustain => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }],
Skill::SustainPlus => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }],
Skill::SustainPlusPlus => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }],
Skill::Curse => vec![ConstructEffect {effect: Effect::Curse, duration: 2,
meta: Some(EffectMeta::Multiplier(150)), tick: None}],
Skill::CursePlus => vec![ConstructEffect {effect: Effect::Curse, duration: 2,
meta: Some(EffectMeta::Multiplier(200)), tick: None}],
Skill::CursePlusPlus => vec![ConstructEffect {effect: Effect::Curse, duration: 3,
meta: Some(EffectMeta::Multiplier(250)), tick: None}],
Skill::Debuff => vec![ConstructEffect {effect: Effect::Slow, duration: 3,
meta: Some(EffectMeta::Multiplier(50)), tick: None }],
Skill::Decay => vec![ConstructEffect {effect: Effect::Wither, duration: 3,
meta: Some(EffectMeta::Multiplier(50)), tick: None },
ConstructEffect {effect: Effect::Decay, duration: 3,
meta: Some(EffectMeta::Skill(Skill::DecayTick)), tick: None}],
Skill::DecayPlus => vec![ConstructEffect {effect: Effect::Wither, duration: 3,
meta: Some(EffectMeta::Multiplier(35)), tick: None },
ConstructEffect {effect: Effect::Decay, duration: 3,
meta: Some(EffectMeta::Skill(Skill::DecayTickPlus)), tick: None}],
Skill::DecayPlusPlus => vec![ConstructEffect {effect: Effect::Wither, duration: 4,
meta: Some(EffectMeta::Multiplier(20)), tick: None },
ConstructEffect {effect: Effect::Decay, duration: 4,
meta: Some(EffectMeta::Skill(Skill::DecayTickPlusPlus)), tick: None}],
Skill::Haste => vec![ConstructEffect {effect: Effect::Haste, duration: 3,
meta: Some(EffectMeta::Multiplier(150)), tick: None }],
Skill::HastePlus => vec![ConstructEffect {effect: Effect::Haste, duration: 4,
meta: Some(EffectMeta::Multiplier(175)), tick: None }],
Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 5,
meta: Some(EffectMeta::Multiplier(225)), tick: None }],
Skill::Absorb => vec![ConstructEffect {effect: Effect::Absorb, duration: 2,
meta: Some(EffectMeta::Skill(Skill::Absorption)), tick: None}],
Skill::AbsorbPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 3,
meta: Some(EffectMeta::Skill(Skill::AbsorptionPlus)), tick: None}],
Skill::AbsorbPlusPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 4,
meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}],
Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 3, meta: None, tick: None}],
Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}],
Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}],
Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3,
meta: Some(EffectMeta::Multiplier(150)), tick: None }],
Skill::HybridPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 4,
meta: Some(EffectMeta::Multiplier(175)), tick: None }],
Skill::HybridPlusPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 5,
meta: Some(EffectMeta::Multiplier(225)), tick: None }],
Skill::Invert => vec![ConstructEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}],
Skill::InvertPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 3, meta: None, tick: None}],
Skill::InvertPlusPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 4, meta: None, tick: None}],
Skill::Counter => vec![ConstructEffect {effect: Effect::Counter, duration: 1,
meta: Some(EffectMeta::Skill(Skill::CounterAttack)), tick: None}],
Skill::CounterPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1,
meta: Some(EffectMeta::Skill(Skill::CounterAttackPlus)), tick: None}],
Skill::CounterPlusPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1,
meta: Some(EffectMeta::Skill(Skill::CounterAttackPlusPlus)), tick: None}],
Skill::Reflect => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }],
Skill::ReflectPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }],
Skill::ReflectPlusPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }],
Skill::Break => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None},
ConstructEffect {effect: Effect::Vulnerable, duration: 3,
meta: Some(EffectMeta::Multiplier(150)), tick: None}],
Skill::BreakPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None},
ConstructEffect {effect: Effect::Vulnerable, duration: 4,
meta: Some(EffectMeta::Multiplier(200)), tick: None}],
Skill::BreakPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None},
ConstructEffect {effect: Effect::Vulnerable, duration: 4,
meta: Some(EffectMeta::Multiplier(250)), tick: None}],
Skill::Ruin => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}],
Skill::RuinPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}],
Skill::RuinPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::Purge => vec![ConstructEffect {effect: Effect::Purge, duration: 2, meta: None, tick: None}],
Skill::PurgePlus => vec![ConstructEffect {effect: Effect::Purge, duration: 3, meta: None, tick: None}],
Skill::PurgePlusPlus => vec![ConstructEffect {effect: Effect::Purge, duration: 4, meta: None, tick: None}],
Skill::Link => vec![ConstructEffect {effect: Effect::Stun, duration: 3, meta: None, tick: None}],
Skill::LinkPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::LinkPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}],
Skill::Silence => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}],
Skill::SilencePlus => vec![ConstructEffect {effect: Effect::Silence, duration: 3, meta: None, tick: None}],
Skill::SilencePlusPlus => vec![ConstructEffect {effect: Effect::Silence, duration: 4, meta: None, tick: None}],
Skill::Siphon => vec![ConstructEffect {effect: Effect::Siphon, duration: 2,
meta: Some(EffectMeta::Skill(Skill::SiphonTick)), tick: None}],
Skill::SiphonPlus => vec![ConstructEffect {effect: Effect::Siphon, duration: 3,
meta: Some(EffectMeta::Skill(Skill::SiphonTickPlus)), tick: None}],
Skill::SiphonPlusPlus => vec![ConstructEffect {effect: Effect::Siphon, duration: 4,
meta: Some(EffectMeta::Skill(Skill::SiphonTickPlusPlus)), tick: None}],
Skill::Sleep => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::SleepPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 3, meta: None, tick: None}],
Skill::SleepPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 4, meta: None, tick: None}],
Skill::Restrict => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}],
Skill::RestrictPlus => vec![ConstructEffect {effect: Effect::Restrict, duration: 3, meta: None, tick: None}],
Skill::RestrictPlusPlus => vec![ConstructEffect {effect: Effect::Restrict, duration: 4, meta: None, tick: None}],
Skill::Bash => vec![ConstructEffect {effect: Effect::Stun, duration: 2,
meta: Some(EffectMeta::Skill(Skill::Bash)), tick: None}],
Skill::BashPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2,
meta: Some(EffectMeta::Skill(Skill::BashPlus)), tick: None}],
Skill::BashPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2,
meta: Some(EffectMeta::Skill(Skill::BashPlusPlus)), tick: None}],
Skill::Stun => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::Intercept => vec![ConstructEffect {effect: Effect::Intercept, duration: 2, meta: None, tick: None}],
Skill::InterceptPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 3, meta: None, tick: None}],
Skill::InterceptPlusPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 4, meta: None, tick: None}],
Skill::Triage => vec![ConstructEffect {effect: Effect::Triage, duration: 2,
meta: Some(EffectMeta::Skill(Skill::TriageTick)), tick: None}],
Skill::TriagePlus => vec![ConstructEffect {effect: Effect::Triage, duration: 3,
meta: Some(EffectMeta::Skill(Skill::TriageTickPlus)), tick: None}],
Skill::TriagePlusPlus => vec![ConstructEffect {effect: Effect::Triage, duration: 4,
meta: Some(EffectMeta::Skill(Skill::TriageTickPlusPlus)), 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::Strike=> None,
Skill::StrikePlus => None,
Skill::StrikePlusPlus => None,
Skill::Block => None, // reduce damage
Skill::Counter|
Skill::CounterPlus |
Skill::CounterPlusPlus => None, // avoid all damage
Skill::Restrict=> Some(2),
Skill::RestrictPlus => Some(2),
Skill::RestrictPlusPlus => Some(2),
Skill::Stun => Some(2),
Skill::Bash=> Some(2),
Skill::BashPlus => Some(2),
Skill::BashPlusPlus => Some(2),
Skill::Heal=> None,
Skill::HealPlus => None,
Skill::HealPlusPlus => None,
Skill::Triage=> None, // hot
Skill::TriagePlus => None, // hot
Skill::TriagePlusPlus => None, // hot
Skill::Break=> Some(1), // no damage stun, adds vulnerable
Skill::BreakPlus => Some(1),
Skill::BreakPlusPlus => Some(1),
Skill::Blast=> None,
Skill::BlastPlus => None,
Skill::BlastPlusPlus => None,
Skill::Chaos=> None,
Skill::ChaosPlus => None,
Skill::ChaosPlusPlus => None,
Skill::Amplify=> Some(1),
Skill::AmplifyPlus => Some(1),
Skill::AmplifyPlusPlus => Some(1),
Skill::Hybrid|
Skill::HybridPlus |
Skill::HybridPlusPlus => Some(1),
Skill::Invert=> Some(2),
Skill::InvertPlus => Some(2),
Skill::InvertPlusPlus => Some(2),
Skill::Decay=> Some(1), // dot
Skill::DecayPlus => Some(1),
Skill::DecayPlusPlus => Some(1),
Skill::Siphon|
Skill::SiphonPlus |
Skill::SiphonPlusPlus => None,
Skill::Curse=> Some(1),
Skill::CursePlus => Some(1),
Skill::CursePlusPlus => Some(1),
Skill::Link=> Some(2),
Skill::LinkPlus => Some(2),
Skill::LinkPlusPlus => Some(2),
Skill::Silence=> Some(2),
Skill::SilencePlus => Some(2),
Skill::SilencePlusPlus => Some(2),
Skill::Purify |
Skill::PurifyPlus |
Skill::PurifyPlusPlus => None,
Skill::Purge=> Some(1),
Skill::PurgePlus => Some(1),
Skill::PurgePlusPlus => Some(1),
Skill::Banish |
Skill::BanishPlus |
Skill::BanishPlusPlus => Some(2),
Skill::Haste=> Some(1),
Skill::HastePlus => Some(1),
Skill::HastePlusPlus => Some(1),
Skill::Reflect |
Skill::ReflectPlus |
Skill::ReflectPlusPlus => None,
Skill::Recharge=> None,
Skill::RechargePlus => None,
Skill::RechargePlusPlus => None,
Skill::Ruin=> Some(3),
Skill::RuinPlus => Some(2),
Skill::RuinPlusPlus => Some(2),
Skill::Slay=> None,
Skill::SlayPlus => None,
Skill::SlayPlusPlus => None,
Skill::Sleep=> Some(2),
Skill::SleepPlus => Some(2),
Skill::SleepPlusPlus => Some(2),
Skill::Sustain |
Skill::SustainPlus |
Skill::SustainPlusPlus => Some(1),
Skill::Intercept=> Some(2),
Skill::InterceptPlus => Some(2),
Skill::InterceptPlusPlus => Some(2),
Skill::Electrify |
Skill::ElectrifyPlus |
Skill::ElectrifyPlusPlus => None,
Skill::Absorb|
Skill::AbsorbPlus |
Skill::AbsorbPlusPlus => Some(1),
//-----------
// Never cast directly
//---------
// Trigger
Skill::HybridBlast |
Skill::HasteStrike |
Skill::CounterAttack|
Skill::CounterAttackPlus |
Skill::CounterAttackPlusPlus | // counter
Skill::Electrocute|
Skill::ElectrocutePlus |
Skill::ElectrocutePlusPlus |
Skill::Absorption|
Skill::AbsorptionPlus |
Skill::AbsorptionPlusPlus |
// Ticks
Skill::ElectrocuteTick|
Skill::ElectrocuteTickPlus |
Skill::ElectrocuteTickPlusPlus |
Skill::DecayTick|
Skill::DecayTickPlus |
Skill::DecayTickPlusPlus |
Skill::SiphonTick|
Skill::SiphonTickPlus |
Skill::SiphonTickPlusPlus |
Skill::TriageTick|
Skill::TriageTickPlus |
Skill::TriageTickPlusPlus => None,
}
}
pub fn ko_castable(&self) -> bool {
match self {
Skill::ElectrocuteTick|
Skill::ElectrocuteTickPlus |
Skill::ElectrocuteTickPlusPlus |
Skill::DecayTick|
Skill::DecayTickPlus |
Skill::DecayTickPlusPlus |
Skill::SiphonTick|
Skill::SiphonTickPlus |
Skill::SiphonTickPlusPlus |
Skill::TriageTick|
Skill::TriageTickPlus |
Skill::TriageTickPlusPlus => true,
_ => false,
}
}
pub fn is_tick(&self) -> bool {
match self {
Skill::ElectrocuteTick|
Skill::ElectrocuteTickPlus |
Skill::ElectrocuteTickPlusPlus |
Skill::DecayTick|
Skill::DecayTickPlus |
Skill::DecayTickPlusPlus |
Skill::SiphonTick|
Skill::SiphonTickPlus |
Skill::SiphonTickPlusPlus |
Skill::TriageTick|
Skill::TriageTickPlus |
Skill::TriageTickPlusPlus => true,
_ => false,
}
}
pub fn speed(&self) -> u64 {
match self {
Skill::SiphonTick|
Skill::SiphonTickPlus |
Skill::SiphonTickPlusPlus => Skill::Siphon.speed(),
Skill::DecayTick|
Skill::DecayTickPlus |
Skill::DecayTickPlusPlus => Skill::Decay.speed(),
Skill::TriageTick|
Skill::TriageTickPlus |
Skill::TriageTickPlusPlus => Skill::Triage.speed(),
Skill::ElectrocuteTick|
Skill::ElectrocuteTickPlus |
Skill::ElectrocuteTickPlusPlus => Skill::Electrify.speed(),
_ => Item::from(*self).speed(),
}
}
pub fn aoe(&self) -> bool {
match self {
Skill::Ruin|
Skill::RuinPlus |
Skill::RuinPlusPlus => true,
_ => false,
}
}
pub fn defensive(&self) -> bool {
let mut rng = thread_rng();
match self {
Skill::Amplify|
Skill::AmplifyPlus |
Skill::AmplifyPlusPlus |
Skill::Block |
Skill::Sustain|
Skill::SustainPlus |
Skill::SustainPlusPlus |
Skill::Electrify|
Skill::ElectrifyPlus |
Skill::ElectrifyPlusPlus |
Skill::Haste|
Skill::HastePlus |
Skill::HastePlusPlus |
Skill::Heal|
Skill::HealPlus |
Skill::HealPlusPlus |
Skill::Absorb|
Skill::AbsorbPlus |
Skill::AbsorbPlusPlus |
Skill::Invert|
Skill::InvertPlus |
Skill::InvertPlusPlus |
Skill::Intercept|
Skill::InterceptPlus |
Skill::InterceptPlusPlus |
Skill::Counter|
Skill::CounterPlus |
Skill::CounterPlusPlus |
Skill::Purify|
Skill::PurifyPlus |
Skill::PurifyPlusPlus |
Skill::Recharge|
Skill::RechargePlus |
Skill::RechargePlusPlus |
Skill::Reflect|
Skill::ReflectPlus |
Skill::ReflectPlusPlus |
Skill::Triage|
Skill::TriagePlus |
Skill::TriagePlusPlus => true,
Skill::Banish|
Skill::BanishPlus |
Skill::BanishPlusPlus => rng.gen_bool(0.5),
_ => false,
}
}
fn components(&self) -> Vec<Item> {
let mut components = Item::from(*self).components();
components.sort_unstable();
return components;
}
pub 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 attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_power().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 Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_power().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 stun(source: &mut Construct, target: &mut Construct, 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 bash(source: &mut Construct, target: &mut Construct, 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)))));
if results.iter().any(|r| match r.event {
Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ }
=> effect == Effect::Stun && skill == effect_skill,
_ => false,
}) {
let mut cds = 0;
for cs in target.skills.iter_mut() {
if cs.skill.base_cd().is_some() {
cs.cd = match cs.cd {
None => Some(1),
Some(i) => Some(i + 1),
};
cds += 1;
}
}
let amount = source.red_power().pct(skill.multiplier().pct(100 + 45u64.saturating_mul(cds)));
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly)));
}
return results;
}
fn sleep(source: &mut Construct, target: &mut Construct, 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_power().pct(skill.multiplier());
target.deal_green_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly)));
return results;
}
fn sustain(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let red_amount = source.red_power().pct(skill.multiplier());
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
let e = target.recharge(skill, red_amount, 0);
let stages = match e {
Event::Recharge { red, blue, skill: _ } => {
if red > 0 || blue > 0 { EventStages::PostOnly }
else { EventStages::NoStages }
}
_ => panic!("not recharge")
};
results.push(Resolution::new(source, target).event(e).stages(stages));
return results;
}
fn intercept(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let intercept = skill.effect()[0];
results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept)));
let red_amount = source.red_power().pct(skill.multiplier());
let e = target.recharge(skill, red_amount, 0);
let stages = match e {
Event::Recharge { red, blue, skill: _ } => {
if red > 0 || blue > 0 { EventStages::PostOnly }
else { EventStages::NoStages }
}
_ => panic!("not recharge")
};
results.push(Resolution::new(source, target).event(e).stages(stages));
return results;
}
fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let stun = skill.effect()[0];
results.push(Resolution::new(source, target).event(target.add_effect(skill, stun)));
let vuln = skill.effect()[1];
results.push(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly));
return results;
}
fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;
}
fn buff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target)
.event(target.add_effect(skill, skill.effect()[0])));
return results;
}
fn counter(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target)
.event(target.add_effect(skill, skill.effect()[0])));
return results;
}
fn counter_attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_power().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 restrict(source: &mut Construct, target: &mut Construct, 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_power().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).stages(EventStages::PostOnly)));
return results;
}
fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_power().pct(skill.multiplier()) + source.green_power().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.pct(50));
for h in heal {
results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly));
};
},
_ => results.push(Resolution::new(source, target).event(e)),
}
}
return results;
}
fn heal(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.green_power().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 Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let skip_tick = target.effects.iter().any(|e| {
match e.effect {
Effect::Triage => source.skill_speed(skill) <= e.tick.unwrap().speed,
_ => false,
}
});
let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0];
let tick_skill = match meta {
Some(EffectMeta::Skill(s)) => s,
_ => panic!("no triage tick skill"),
};
let triage = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill));
results.push(Resolution::new(source, target).event(target.add_effect(skill, triage)));
match skip_tick {
true => return results,
false => return triage_tick(source, target, results, tick_skill)
}
}
fn triage_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.green_power().pct(skill.multiplier());
target.deal_green_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)));
return results;
}
fn chaos(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let mut rng = thread_rng();
let b_rng: u64 = rng.gen_range(100, 130);
let amount = source.blue_power().pct(skill.multiplier()).pct(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(100, 130);
let amount = source.red_power().pct(skill.multiplier()).pct(r_rng);
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly)));
return results;
}
fn blast(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_power().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 Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;;
}
fn haste(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;;
}
fn debuff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;;
}
fn decay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let wither = skill.effect()[0];
results.push(Resolution::new(source, target).event(target.add_effect(skill, wither)));
let skip_tick = target.effects.iter().any(|e| {
match e.effect {
Effect::Decay => source.skill_speed(skill) <= e.tick.unwrap().speed,
_ => false,
}
});
let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[1];
let tick_skill = match meta {
Some(EffectMeta::Skill(s)) => s,
_ => panic!("no decay tick skill"),
};
let decay = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill));
results.push(Resolution::new(source, target)
.event(target.add_effect(skill, decay))
.stages(EventStages::PostOnly));
match skip_tick {
true => return results,
false => return decay_tick(source, target, results, tick_skill)
}
}
fn decay_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_power().pct(skill.multiplier());
target.deal_blue_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)));
return results;
}
// electrify is the buff effect
// when attacked it runs electrocute and applies a debuff
fn electrify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let electrify = skill.effect()[0];
results.push(Resolution::new(source, target).event(target.add_effect(skill, electrify)));
return results;;
}
fn electrocute(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
// Remove electric buff, no need to display if construct is dead
if !source.is_ko() {
let electric = source.effects.iter().position(|e| e.effect == Effect::Electric);
match electric {
Some(eff) => {
let ce = source.effects.remove(eff);
results.push(Resolution::new(source, source)
.event(Event::Removal { effect: ce.effect, construct_effects: source.effects.clone() })
.stages(EventStages::PostOnly));
}
None => ()
}
}
let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0];
let tick_skill = match meta {
Some(EffectMeta::Skill(s)) => s,
_ => panic!("no electrocute tick skill"),
};
let skip_tick = target.effects.iter().any(|e| {
match e.effect {
Effect::Electrocute => source.skill_speed(skill) <= e.tick.unwrap().speed,
_ => false,
}
});
let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill));
results.push(Resolution::new(source, target)
.event(target.add_effect(skill, electrocute))
.stages(EventStages::PostOnly));
match skip_tick {
true => return results,
false => return electrocute_tick(source, target, results, tick_skill)
}
}
fn electrocute_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_power().pct(skill.multiplier());
target.deal_blue_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)));
return results;
}
fn ruin(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target)
.event(target.add_effect(skill, skill.effect()[0]))
.stages(EventStages::PostOnly));
return results;;
}
fn absorb(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;;
}
fn absorption(source: &mut Construct, target: &mut Construct, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions {
let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount));
results.push(Resolution::new(source, target)
.event(target.add_effect(reflect_skill, absorb))
.stages(EventStages::PostOnly));
let absorb_index = target.effects.iter().position(|e| e.effect == Effect::Absorb).expect("No absorb");
let ce = target.effects.remove(absorb_index);
results.push(Resolution::new(source, target)
.event(Event::Removal { effect: ce.effect, construct_effects: target.effects.clone() })
.stages(EventStages::PostOnly));
return results;;
}
fn curse(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;;
}
fn hybrid(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;;
}
fn invert(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
return results;;
}
fn reflect(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
let blue_amount = source.blue_power().pct(skill.multiplier());
let e = target.recharge(skill, 0, blue_amount);
let stages = match e {
Event::Recharge { red, blue, skill: _ } => {
if red > 0 || blue > 0 { EventStages::PostOnly }
else { EventStages::NoStages }
}
_ => panic!("not recharge")
};
results.push(Resolution::new(source, target).event(e).stages(stages));
return results;;
}
fn recharge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let red_amount = source.red_power().pct(skill.multiplier());
let blue_amount = source.blue_power().pct(skill.multiplier());
let e = target.recharge(skill, red_amount, blue_amount);
let stages = match e {
Event::Recharge { red, blue, skill: _ } => {
if red > 0 || blue > 0 { EventStages::AllStages }
else { EventStages::StartEnd }
}
_ => panic!("not recharge")
};
results.push(Resolution::new(source, target).event(e).stages(stages));
return results;
}
fn siphon(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let skip_tick = target.effects.iter().any(|e| {
match e.effect {
Effect::Siphon => source.skill_speed(skill) <= e.tick.unwrap().speed,
_ => false,
}
});
let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0];
let tick_skill = match meta {
Some(EffectMeta::Skill(s)) => s,
_ => panic!("no siphon tick skill"),
};
let siphon = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill));
results.push(Resolution::new(source, target).event(target.add_effect(skill, siphon)));
match skip_tick {
true => return results,
false => return siphon_tick(source, target, results, tick_skill)
}
}
fn siphon_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.blue_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier());
let siphon_events = target.deal_blue_damage(skill, amount);
for e in siphon_events {
match e {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost));
let heal = source.deal_green_damage(skill, amount);
for h in heal {
results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly));
};
},
_ => results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)),
}
}
return results;
}
fn link(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let swap = match target.green_life().checked_sub(source.green_life()) {
Some(s) => s.pct(skill.multiplier()),
None => 0
};
target.deal_blue_damage(skill, swap)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
source.deal_green_damage(skill, swap)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, source).event(e).stages(EventStages::PostOnly)));
results.push(Resolution::new(source, source)
.event(source.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly));
return results;
}
fn silence(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])));
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_power().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).stages(EventStages::PostOnly)));
return results;
}
fn purge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd));
while let Some(i) = target.effects
.iter()
.position(|ce| {
if let Some(c) = ce.effect.colour() {
c == Colour::Green
} else {
false
}
}) {
let ce = target.effects.remove(i);
results.push(Resolution::new(source, target)
.event(Event::Removal { effect: ce.effect, construct_effects: target.effects.clone() })
.stages(EventStages::PostOnly));
}
let effect = skill.effect()[0];
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly));
/*let mut turns = 1;
for cs in target.skills.iter_mut() {
if Effect::Purge.disables_skill(cs.skill) {
turns += 1;
}
}
if turns > 1 {
effect.duration = effect.duration * turns;
}*/
return results;
}
fn purify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd));
let amount = source.green_power().pct(skill.multiplier());
while let Some(i) = target.effects
.iter()
.position(|ce| {
if let Some(c) = ce.effect.colour() {
[Colour::Red, Colour::Blue].contains(&c)
} else {
false
}
}) {
let ce = target.effects.remove(i);
results.push(Resolution::new(source, target)
.event(Event::Removal { effect: ce.effect, construct_effects: target.effects.clone() })
.stages(EventStages::PostOnly));
target.deal_green_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly)));
}
return results;
}
fn banish(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd));
let red_damage = target.red_life().pct(skill.multiplier());
let blue_damage = target.blue_life().pct(skill.multiplier());
if red_damage > 0 {
target.deal_red_damage(skill, red_damage)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly)));
}
if blue_damage > 0 {
target.deal_blue_damage(skill, blue_damage)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly)));
}
results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly));
return results;
}
#[cfg(test)]
mod tests {
use skill::*;
#[test]
fn heal_test() {
let mut x = Construct::new()
.named(&"muji".to_string())
.learn(Skill::Heal);
let mut y = Construct::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 = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::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 = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::new()
.named(&"camel".to_string());
// ensure it doesn't have 0 pd
x.red_power.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, stages: _ } = results.remove(0);
match event {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } =>
assert!(amount < x.red_power().pct(Skill::Attack.multiplier())),
_ => panic!("not damage"),
};
}
#[test]
fn sustain_test() {
let mut x = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::new()
.named(&"camel".to_string());
x.red_power.force(10000000000000); // multiplication of int max will cause overflow
y.green_life.force(1024); // make tests more flexible if we change stats
sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain);
assert!(y.affected(Effect::Sustain));
let mut results = ruin(&mut x, &mut y, vec![], Skill::Ruin);
let Resolution { source: _, target: _, event, stages: _ } = results.remove(0);
match event {
Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Sustain)),
_ => 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, stages: _ } = results.remove(0);
match event {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023),
_ => panic!("not damage"),
};
}
#[test]
fn invert_test() {
let mut x = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::new()
.named(&"camel".to_string());
// give red shield but reduce to 0
y.red_life.force(64);
y.red_life.reduce(64);
x.red_power.force(512);
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);
// attack should heal and recharge red shield
let mut results = attack(&mut x, &mut y, vec![], Skill::Attack);
// 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!(amount > 0),
_ => panic!("not healing from inversion"),
};
match results.remove(0).event {
Event::Recharge { skill: _, red, blue: _ } => assert!(red > 0),
_ => panic!("not recharge from inversion"),
};
}
#[test]
fn reflect_test() {
let mut x = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::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::Blast, &mut x, &mut y, results);
assert!(x.green_life() < 1024);
let Resolution { source: _, target: _, event, stages: _ } = results.remove(0);
match event {
Event::Reflection { skill } => assert_eq!(skill, Skill::Blast),
_ => panic!("not reflection"),
};
let Resolution { source: _, target: _, event, stages: _ } = results.remove(0);
match event {
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert!(amount > 0),
_ => panic!("not damage"),
};
}
#[test]
fn siphon_test() {
let mut x = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::new()
.named(&"camel".to_string());
x.blue_power.force(256);
x.green_power.force(220);
x.green_life.force(1024);
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()) + 220.pct(Skill::SiphonTick.multiplier())));
let Resolution { source: _, target: _, event, stages: _ } = results.remove(0);
match event {
Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Siphon),
_ => panic!("not siphon"),
};
let Resolution { source: _, target: _, event, stages: _ } = results.remove(0);
match event {
Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier())
+ 220.pct(Skill::SiphonTick.multiplier())),
_ => panic!("not damage siphon"),
};
let Resolution { source: _, target, event, stages: _ } = results.remove(0);
match event {
Event::Healing { amount, skill: _, overhealing: _ } => {
assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()));
assert_eq!(target.id, x.id);
},
_ => panic!("not healing"),
};
}
#[test]
fn triage_test() {
let mut x = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::new()
.named(&"pretaliation".to_string());
// ensure it doesn't have 0 sd
x.blue_power.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 = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::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, stages: _ } = 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 = Construct::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 = Construct::new()
.named(&"muji".to_string());
x.blue_power.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_power(), 75);
}
#[test]
fn purify_test() {
let mut x = Construct::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));
}
#[test]
fn bash_test() {
let mut x = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::new()
.named(&"pretaliation".to_string())
.learn(Skill::Stun);
let stun_cd = y.skills.iter().find(|cs| cs.skill == Skill::Stun).unwrap().cd.unwrap();
bash(&mut x, &mut y, vec![], Skill::Bash);
assert!(!x.effects.iter().any(|e| e.effect == Effect::Stun));
assert!(y.skills.iter().any(|cs| cs.skill == Skill::Stun && cs.cd.unwrap() == stun_cd + 1));
}
#[test]
fn purge_test() {
let mut x = Construct::new()
.named(&"muji".to_string());
let mut y = Construct::new()
.named(&"pretaliation".to_string())
.learn(Skill::Heal)
.learn(Skill::HealPlus);
purge(&mut x, &mut y, vec![], Skill::Purge);
// 2 turns at lvl 1
assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2));
assert!(y.disabled(Skill::Heal).is_some());
}
}