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) -> 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; pub type Immunity = Vec; // 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 }, Removal { effect: Effect, construct_effects: Vec }, 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; #[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=> 110, // BB Skill::BlastPlus => 145, // BB Skill::BlastPlusPlus => 210, // 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=> 20, // GB Skill::SiphonTickPlus => 25, Skill::SiphonTickPlusPlus => 30, Skill::Slay=> 40, // RG Skill::SlayPlus => 60, Skill::SlayPlusPlus => 90, Skill::Strike=> 90, //RR Skill::StrikePlus => 140, Skill::StrikePlusPlus => 200, // Block Base Skill::ElectrocuteTick=> 80, Skill::ElectrocuteTickPlus => 100, Skill::ElectrocuteTickPlusPlus => 130, Skill::CounterAttack=> 70, Skill::CounterAttackPlus => 95, Skill::CounterAttackPlusPlus => 120, 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=> 240, //Green dmg (heal) Skill::SleepPlus => 300, Skill::SleepPlusPlus => 400, Skill::Banish=> 40, //Green dmg (heal) Skill::BanishPlus => 75, Skill::BanishPlusPlus => 125, Skill::Bash=> 65, Skill::BashPlus => 95, Skill::BashPlusPlus => 140, // 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 => 25, Skill::HasteStrike => 30, Skill::Link=> 75, Skill::LinkPlus => 100, Skill::LinkPlusPlus => 150, Skill::Intercept=> 80, Skill::InterceptPlus => 110, Skill::InterceptPlusPlus => 150, Skill::TriageTick=> 75, Skill::TriageTickPlus => 110, Skill::TriageTickPlusPlus => 140, _ => 100, } } pub fn effect(&self) -> Vec { 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(50)), tick: None}], Skill::Buff => vec![ConstructEffect {effect: Effect::Buff, duration: 2, meta: Some(EffectMeta::Multiplier(125)), 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: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None }], Skill::HastePlus => vec![ConstructEffect {effect: Effect::Haste, duration: 3, meta: Some(EffectMeta::Multiplier(175)), tick: None }], Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 4, 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: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None }], Skill::HybridPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3, meta: Some(EffectMeta::Multiplier(175)), tick: None }], Skill::HybridPlusPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 4, 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}, ConstructEffect {effect: Effect::Block, duration: 1, meta: Some(EffectMeta::Multiplier(60)), tick: None}], Skill::CounterPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, meta: Some(EffectMeta::Skill(Skill::CounterAttackPlus)), tick: None}, ConstructEffect {effect: Effect::Block, duration: 1, meta: Some(EffectMeta::Multiplier(40)), tick: None}], Skill::CounterPlusPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, meta: Some(EffectMeta::Skill(Skill::CounterAttackPlusPlus)), tick: None}, ConstructEffect {effect: Effect::Block, duration: 1, meta: Some(EffectMeta::Multiplier(20)), 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(3), 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(3), 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(3), Skill::Haste=> Some(2), Skill::HastePlus => Some(2), Skill::HastePlusPlus => Some(2), 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(3), Skill::SleepPlus => Some(3), Skill::SleepPlusPlus => Some(3), 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 { let mut components = Item::from(*self).components(); components.sort_unstable(); return components; } pub fn colours(&self) -> Vec { 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::>(); } 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]))); results.push(Resolution::new(source, target) .event(target.add_effect(skill, skill.effect()[1])) .stages(EventStages::PostOnly)); 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).stages(EventStages::StartPost))); 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); 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()); } }