use rand::{thread_rng, Rng}; use uuid::Uuid; use util::{IntPct}; use construct::{Construct, ConstructEffect, EffectMeta, Stat}; use item::{Item}; use game::{Game}; pub fn resolution_steps(cast: &Cast, game: &mut Game) -> Resolutions { let mut resolutions = vec![]; resolutions = pre_resolve(cast, game, resolutions); return resolutions; } pub fn pre_resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { let skill = cast.skill; let source = game.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::Skill { skill })); } for target_id in targets { // we clone the current state of the target and source // so we can modify them during the resolution // no more than 1 mutable ref allowed on game let mut source = game.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 })); return resolutions; } if target.is_ko() { resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill })); return resolutions; } if target.affected(Effect::Reflect) { // guard against overflow if source.affected(Effect::Reflect) { return resolutions; } resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill })); return resolve(skill, target, source, resolutions); } if source.affected(Effect::Haste) { match skill { Skill::Attack | Skill::SlayI | Skill::ChaosI | Skill::StrikeI => { let amount = source.speed().pct(Skill::HasteStrike.multiplier()); target.deal_red_damage(Skill::HasteStrike, amount) .into_iter() .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); }, _ => (), } } if source.affected(Effect::Impurity) { match skill { Skill::BlastI | Skill::ChaosI | Skill::SiphonI => { let amount = source.green_power().pct(Skill::ImpureBlast.multiplier()); target.deal_blue_damage(Skill::ImpureBlast, amount) .into_iter() .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); }, _ => (), } } // match self.category() == EffectCategory::Red { // true => { // if let Some(evasion) = target.evade(*self) { // resolutions.push(evasion); // return Event; // } // }, // false => (), // } resolutions = match skill { Skill::AmplifyI | Skill::AmplifyII | Skill::AmplifyIII => amplify(source, target, resolutions, skill), Skill::BanishI | Skill::BanishII | Skill::BanishIII => banish(source, target, resolutions, skill), // TODO prevent all actions Skill::BlastI | Skill::BlastII | Skill::BlastIII => blast(source, target, resolutions, skill), Skill::ChaosI | Skill::ChaosII | Skill::ChaosIII => chaos(source, target, resolutions, skill), Skill::ClutchI | Skill::ClutchII | Skill::ClutchIII => clutch(source, target, resolutions, skill), Skill::CorruptI | Skill::CorruptII | Skill::CorruptIII => corrupt(source, target, resolutions, skill), Skill::CorruptionTickI | Skill::CorruptionTickII | Skill::CorruptionTickIII => corruption_tick(source, target, resolutions, skill), Skill::CurseI | Skill::CurseII | Skill::CurseIII => curse(source, target, resolutions, skill), Skill::DecayI | Skill::DecayII | Skill::DecayIII => decay(source, target, resolutions, skill), // dot Skill::DecayTickI | Skill::DecayTickII | Skill::DecayTickIII => decay_tick(source, target, resolutions, skill), // dot Skill::HasteI | Skill::HasteII | Skill::HasteIII => haste(source, target, resolutions, skill), // speed slow Skill::HealI | Skill::HealII | Skill::HealIII => heal(source, target, resolutions, skill), Skill::HexI | Skill::HexII | Skill::HexIII => hex(source, target, resolutions, skill), Skill::HostilityI | Skill::HostilityII | Skill::HostilityIII => hostility(source, target, resolutions, skill), Skill::ImpurityI | Skill::ImpurityII | Skill::ImpurityIII => impurity(source, target, resolutions, skill), Skill::InvertI | Skill::InvertII | Skill::InvertIII => invert(source, target, resolutions, skill), Skill::ParryI | Skill::ParryII | Skill::ParryIII => parry(source, target, resolutions, skill), Skill::PurgeI | Skill::PurgeII | Skill::PurgeIII => purge(source, target, resolutions, skill), // dispel all buffs Skill::PurifyI | Skill::PurifyII | Skill::PurifyIII => purify(source, target, resolutions, skill), Skill::RechargeI | Skill::RechargeII | Skill::RechargeIII => recharge(source, target, resolutions, skill), Skill::ReflectI | Skill::ReflectII | Skill::ReflectIII => reflect(source, target, resolutions, skill), Skill::RuinI | Skill::RuinII | Skill::RuinIII => ruin(source, target, resolutions, skill), Skill::ScatterI | Skill::ScatterII | Skill::ScatterIII => scatter(source, target, resolutions, skill), // target is immune to magic damage and fx Skill::SilenceI | Skill::SilenceII | Skill::SilenceIII => silence(source, target, resolutions, skill), // target cannot cast spells Skill::SiphonI | Skill::SiphonII | Skill::SiphonIII => siphon(source, target, resolutions, skill), // dot Skill::SiphonTickI | Skill::SiphonTickII | Skill::SiphonTickIII => siphon_tick(source, target, resolutions, skill), // dot Skill::SlayI | Skill::SlayII | Skill::SlayIII => slay(source, target, resolutions, skill), // hybrid dmg self heal Skill::SleepI | Skill::SleepII | Skill::SleepIII => sleep(source, target, resolutions, skill), // heal stun Skill::SnareI | Skill::SnareII | Skill::SnareIII => snare(source, target, resolutions, skill), Skill::StrangleI | Skill::StrangleII | Skill::StrangleIII => strangle(source, target, resolutions, skill), Skill::StrangleTickI | Skill::StrangleTickII | Skill::StrangleTickIII => strangle_tick(source, target, resolutions, skill), Skill::StrikeI | Skill::StrikeII | Skill::StrikeIII => strike(source, target, resolutions, skill), Skill::TauntI | Skill::TauntII | Skill::TauntIII => taunt(source, target, resolutions, skill), Skill::ThrowI | Skill::ThrowII | Skill::ThrowIII => throw(source, target, resolutions, skill), // no damage stun, adds vulnerable Skill::TriageI | Skill::TriageII | Skill::TriageIII => triage(source, target, resolutions, skill), // hot Skill::TriageTickI | Skill::TriageTickII | Skill::TriageTickIII => 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::CorruptionI | Skill::CorruptionII | Skill::CorruptionIII => panic!("should only trigger from corrupt hit"), Skill::HasteStrike => panic!("should only trigger from haste"), Skill::HatredI | Skill::HatredII | Skill::HatredIII => panic!("should only trigger from hatred"), Skill::ImpureBlast => panic!("should only trigger from impurity"), Skill::RiposteI | Skill::RiposteII | Skill::RiposteIII => panic!("should only trigger from parry"), // Not used // Skill::Injure => injure(source, target, resolutions, skill), }; 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: _ } => { if target.affected(Effect::Corrupt) { let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() .find(|e| e.effect == Effect::Corrupt).unwrap().clone(); match meta { Some(EffectMeta::Skill(s)) => { resolutions = corruption(&mut target, &mut source, resolutions, s); }, _ => panic!("no corrupt skill"), }; } if target.affected(Effect::Hostility) { let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() .find(|e| e.effect == Effect::Hostility).unwrap().clone(); match meta { Some(EffectMeta::Skill(s)) => { resolutions = hatred(&mut target, &mut source, resolutions, skill, amount, s); }, _ => panic!("no hatred skill"), }; } // beware that scatter doesn't cause any damage // because then applying it will proc this if target.affected(Effect::Scatter) { resolutions = scatter_hit(&source, &target, resolutions, game, event) } }, Event::Immunity { skill: _, immunity } => match immunity.contains(&Effect::Parry) { true => { let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() .find(|e| e.effect == Effect::Parry).unwrap().clone(); match meta { Some(EffectMeta::Skill(s)) => { resolutions = riposte(&mut target, &mut source, resolutions, s); }, _ => panic!("no parry skill"), }; }, false => (), }, _ => (), }; 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: 0, } } 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 LogStages { AllStages, // 0 Anim Anim Anim StartEnd, // 1 Anim Anim Skip StartPost, // 2 Anim Skip Anim StartOnly, // 3 Anim Skip Skip EndPost, // 4 Skip Anim Anim EndOnly, // 5 Skip Anim Skip PostOnly, // 6 Skip Skip Anim None, // 7 Skip Skip Skip } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub struct Resolution { pub source: EventConstruct, pub target: EventConstruct, pub event: Event, pub stages: u8, } 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: LogStages::AllStages as u8, } } fn event(mut self, e: Event) -> Resolution { self.event = e; self } fn stages(mut self, s: LogStages) -> Resolution { self.stages = s as u8; self } } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub enum Event { Disable { skill: Skill, disable: Disable }, Immunity { skill: Skill, immunity: Immunity }, Damage { skill: Skill, amount: u64, mitigation: u64, colour: Colour }, Healing { skill: Skill, amount: u64, overhealing: u64 }, Recharge { skill: Skill, red: u64, blue: u64 }, Inversion { skill: Skill }, Reflection { skill: Skill }, 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 }, } type Resolutions = Vec; pub type Cooldown = Option; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] enum EffectCategory { Buff, Debuff, Constant, Ko, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Effect { Amplify, Banish, Block, Buff, Clutch, Curse, Haste, Hex, Impurity, Invert, Parry, Reflect, Slow, Snare, Strangle, Strangling, Stun, Taunt, Vulnerable, Silence, Wither, // Reduce green dmg (healing) taken // corrupt is the buff that applies // corruption the dmg debuff Corrupt, Corruption, // hostility is the buff // hatred is the increased damage Hostility, Hatred, // magic immunity Scatter, // effects over time Triage, Decay, Regen, Siphon, Injured, // Airborne, // Boost // Bleed, // Blind, // Deadly, // Enslave, // Fury, // Injured, // Leech, // Mesmerise, // Untouchable, // SpeedSiphon, // SpeedIncrease, Ko, } impl Effect { pub fn immune(&self, skill: Skill) -> bool { match self { Effect::Parry => match skill { Skill::Attack => true, Skill::Stun => true, _ => skill.colours().contains(&Colour::Red) }, Effect::Banish => true, Effect::Clutch => [ Skill::Stun, Skill::HexI, Skill::HexII, Skill::HexIII, Skill::SilenceI, Skill::SilenceII, Skill::SilenceIII, Skill::RuinI, Skill::RuinII, Skill::RuinIII, Skill::StrangleI, Skill::StrangleII, Skill::StrangleIII, Skill::SnareI, Skill::SnareII, Skill::SnareIII ].contains(&skill), Effect::Injured => skill.colours().contains(&Colour::Green), _ => false, } } pub fn disables_skill(&self, skill: Skill) -> bool { if skill.is_tick() { return false; } match self { Effect::Stun => true, Effect::Hex => true, Effect::Banish => true, Effect::Strangle => true, Effect::Strangling => match skill { Skill::StrangleTickI | Skill::StrangleTickII | Skill::StrangleTickIII => false, _ => true, }, Effect::Silence => skill.colours().contains(&Colour::Blue), Effect::Snare => skill.colours().contains(&Colour::Red), Effect::Ko => skill.ko_castable(), _ => false, } } pub fn modifications(&self) -> Vec { match self { Effect::Vulnerable => vec![Stat::RedDamageTaken], Effect::Block => vec![Stat::RedDamageTaken], Effect::Buff => vec![Stat::RedPower, Stat::Speed], Effect::Hatred => vec![Stat::RedPower, Stat::BluePower], Effect::Amplify => vec![Stat::RedPower, Stat::BluePower], Effect::Curse => vec![Stat::BlueDamageTaken], Effect::Impurity => vec![Stat::GreenPower], Effect::Wither => vec![Stat::GreenDamageTaken], Effect::Haste => vec![Stat::Speed], Effect::Slow => vec![Stat::Speed], Effect::Scatter => vec![Stat::BlueDamageTaken, Stat::GreenDamageTaken, Stat::RedDamageTaken], _ => vec![], } } pub fn apply(&self, value: u64, meta: Option) -> u64 { match self { Effect::Amplify | Effect::Vulnerable | Effect::Block | Effect::Buff | Effect::Curse | Effect::Haste | Effect::Slow | Effect::Impurity | Effect::Wither => value.pct(match meta { Some(EffectMeta::Multiplier(d)) => d, _ => 100, }), Effect::Scatter => value >> 1, Effect::Hatred => value + match meta { Some(EffectMeta::AddedDamage(d)) => d, _ => panic!("hatred meta not damage"), }, _ => { info!("{:?} does not have a mod effect", self); return value; }, } } fn category(&self) -> EffectCategory { match self { // physical Effect::Stun => EffectCategory::Debuff, Effect::Block => EffectCategory::Buff, Effect::Buff => EffectCategory::Buff, Effect::Parry => EffectCategory::Buff, Effect::Vulnerable => EffectCategory::Debuff, Effect::Snare => EffectCategory::Debuff, Effect::Clutch => EffectCategory::Buff, Effect::Taunt => EffectCategory::Buff, // magic Effect::Hex => EffectCategory::Debuff, Effect::Curse => EffectCategory::Debuff, Effect::Banish => EffectCategory::Debuff, // todo randomise // Effect::Banish => rng.gen_bool(0.5), Effect::Slow => EffectCategory::Debuff, Effect::Haste => EffectCategory::Buff, Effect::Hatred => EffectCategory::Buff, Effect::Reflect => EffectCategory::Buff, Effect::Amplify => EffectCategory::Buff, Effect::Silence => EffectCategory::Debuff, Effect::Wither => EffectCategory::Debuff, Effect::Corrupt => EffectCategory::Buff, Effect::Corruption => EffectCategory::Debuff, Effect::Hostility => EffectCategory::Buff, // magic Effect::Impurity => EffectCategory::Buff, Effect::Scatter => EffectCategory::Buff, Effect::Invert => EffectCategory::Buff, // effects over time Effect::Triage => EffectCategory::Buff, Effect::Decay => EffectCategory::Debuff, Effect::Regen => EffectCategory::Buff, Effect::Siphon => EffectCategory::Debuff, // can't be purged or purified Effect::Strangle => EffectCategory::Constant, Effect::Strangling => EffectCategory::Constant, // not in game Effect::Injured => EffectCategory::Debuff, Effect::Ko => EffectCategory::Ko, } } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Colour { Red, Blue, Green, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Skill { Attack, Debuff, Buff, Block, // reduce damage Stun, // Boost -- sounds nice // Evade, // actively evade // Nightmare, // Sleep, AmplifyI, AmplifyII, AmplifyIII, BanishI, BanishII, BanishIII, BlastI, BlastII, BlastIII, ChaosI, ChaosII, ChaosIII, ClutchI, ClutchII, ClutchIII, CorruptI, CorruptII, CorruptIII, CorruptionI, CorruptionII, CorruptionIII, CorruptionTickI, CorruptionTickII, CorruptionTickIII, CurseI, CurseII, CurseIII, DecayI, // dot DecayII, DecayIII, DecayTickI, // dot DecayTickII, DecayTickIII, HasteI, HasteII, HasteIII, HasteStrike, HealI, HealII, HealIII, HexI, HexII, HexIII, HatredI, HatredII, HatredIII, HostilityI, HostilityII, HostilityIII, ImpureBlast, ImpurityI, ImpurityII, ImpurityIII, // Injure, InvertI, InvertII, InvertIII, ParryI, // avoid all damage ParryII, ParryIII, PurgeI, PurgeII, PurgeIII, PurifyI, PurifyII, PurifyIII, RechargeI, RechargeII, RechargeIII, ReflectI, ReflectII, ReflectIII, RiposteI, RiposteII, RiposteIII, RuinI, RuinII, RuinIII, ScatterI, ScatterII, ScatterIII, SilenceI, SilenceII, SilenceIII, SiphonI, SiphonII, SiphonIII, SiphonTickI, SiphonTickII, SiphonTickIII, SlayI, SlayII, SlayIII, SleepI, SleepII, SleepIII, SnareI, SnareII, SnareIII, StrangleI, StrangleII, StrangleIII, StrangleTickI, StrangleTickII, StrangleTickIII, StrikeI, StrikeII, StrikeIII, TauntI, TauntII, TauntIII, ThrowI, // no damage stun, adds vulnerable ThrowII, ThrowIII, TriageI, // hot TriageII, TriageIII, TriageTickI, TriageTickII, TriageTickIII, } impl Skill { pub fn multiplier(&self) -> u64 { match self { // Attack Base Skill::Attack => 80, // Base Skill::BlastI => 110, // BB Skill::BlastII => 130, // BB Skill::BlastIII => 150, // BB Skill::ChaosI => 40, // BR Skill::ChaosII => 50, // BR Skill::ChaosIII => 60, // BR Skill::HealI => 130, //GG Skill::HealII => 160, //GG Skill::HealIII => 200, //GG Skill::SiphonTickI => 40, // GB Skill::SiphonTickII => 70, Skill::SiphonTickIII => 110, Skill::SlayI => 70, // RG Skill::SlayII => 90, Skill::SlayIII => 120, Skill::StrikeI => 90, //RR Skill::StrikeII => 110, Skill::StrikeIII => 140, // Block Base Skill::CorruptionTickI => 80, Skill::CorruptionTickII => 100, Skill::CorruptionTickIII => 130, Skill::ParryI => 110, Skill::ParryII => 145, Skill::ParryIII => 200, Skill::RiposteI => 70, Skill::RiposteII => 95, Skill::RiposteIII => 120, Skill::PurifyI => 45, //Green dmg (heal) Skill::PurifyII => 70, Skill::PurifyIII => 105, Skill::ReflectI => 45, //restore blue life (heal) Skill::ReflectII => 70, Skill::ReflectIII => 100, Skill::RechargeI => 85, //restore red and blue life (heal) Skill::RechargeII => 130, Skill::RechargeIII => 200, // Stun Base Skill::SleepI => 240, //Green dmg (heal) Skill::SleepII => 300, Skill::SleepIII => 400, Skill::StrangleTickI => 65, Skill::StrangleTickII => 95, Skill::StrangleTickIII => 140, // Debuff Base Skill::DecayTickI => 25, Skill::DecayTickII => 45, Skill::DecayTickIII => 70, Skill::SilenceI => 55, // Deals more per blue skill on target Skill::SilenceII => 80, Skill::SilenceIII => 110, Skill::SnareI => 40, // Deals more per red skill on target Skill::SnareII => 65, Skill::SnareIII => 100, // Buff base Skill::ImpureBlast => 25, Skill::HasteStrike => 30, Skill::ScatterI => 140, Skill::ScatterII => 200, Skill::ScatterIII => 300, Skill::TauntI => 80, Skill::TauntII => 110, Skill::TauntIII => 150, Skill::TriageTickI => 75, Skill::TriageTickII => 110, Skill::TriageTickIII => 140, _ => 100, } } pub fn effect(&self) -> Vec { match self { // Modifiers Skill::AmplifyI => vec![ConstructEffect {effect: Effect::Amplify, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None}], Skill::AmplifyII => vec![ConstructEffect {effect: Effect::Amplify, duration: 3, meta: Some(EffectMeta::Multiplier(175)), tick: None}], Skill::AmplifyIII => vec![ConstructEffect {effect: Effect::Amplify, duration: 4, meta: Some(EffectMeta::Multiplier(200)), tick: None}], Skill::BanishI => vec![ConstructEffect {effect: Effect::Banish, duration: 1,meta: None, tick: None}], Skill::BanishII => vec![ConstructEffect {effect: Effect::Banish, duration: 2,meta: None, tick: None}], Skill::BanishIII => vec![ConstructEffect {effect: Effect::Banish, duration: 3,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::CorruptI => vec![ConstructEffect {effect: Effect::Corrupt, duration: 2, meta: Some(EffectMeta::Skill(Skill::CorruptionI)), tick: None}], Skill::CorruptII => vec![ConstructEffect {effect: Effect::Corrupt, duration: 3, meta: Some(EffectMeta::Skill(Skill::CorruptionII)), tick: None}], Skill::CorruptIII => vec![ConstructEffect {effect: Effect::Corrupt, duration: 4, meta: Some(EffectMeta::Skill(Skill::CorruptionIII)), tick: None}], Skill::CorruptionI => vec![ConstructEffect {effect: Effect::Corruption, duration: 3, meta: Some(EffectMeta::Skill(Skill::CorruptionTickI)), tick: None}], Skill::CorruptionII => vec![ConstructEffect {effect: Effect::Corruption, duration: 4, meta: Some(EffectMeta::Skill(Skill::CorruptionTickII)), tick: None}], Skill::CorruptionIII => vec![ConstructEffect {effect: Effect::Corruption, duration: 5, meta: Some(EffectMeta::Skill(Skill::CorruptionTickIII)), tick: None}], Skill::ClutchI => vec![ConstructEffect {effect: Effect::Clutch, duration: 1, meta: None, tick: None }], Skill::ClutchII => vec![ConstructEffect {effect: Effect::Clutch, duration: 2, meta: None, tick: None }], Skill::ClutchIII => vec![ConstructEffect {effect: Effect::Clutch, duration: 3, meta: None, tick: None }], Skill::CurseI => vec![ConstructEffect {effect: Effect::Curse, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None}], Skill::CurseII => vec![ConstructEffect {effect: Effect::Curse, duration: 2, meta: Some(EffectMeta::Multiplier(200)), tick: None}], Skill::CurseIII => 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::DecayI => 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::DecayTickI)), tick: None}], Skill::DecayII => 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::DecayTickII)), tick: None}], Skill::DecayIII => 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::DecayTickIII)), tick: None}], Skill::HasteI => vec![ConstructEffect {effect: Effect::Haste, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None }], Skill::HasteII => vec![ConstructEffect {effect: Effect::Haste, duration: 3, meta: Some(EffectMeta::Multiplier(175)), tick: None }], Skill::HasteIII => vec![ConstructEffect {effect: Effect::Haste, duration: 4, meta: Some(EffectMeta::Multiplier(225)), tick: None }], Skill::HexI => vec![ConstructEffect {effect: Effect::Hex, duration: 2, meta: None, tick: None}], Skill::HexII => vec![ConstructEffect {effect: Effect::Hex, duration: 3, meta: None, tick: None}], Skill::HexIII => vec![ConstructEffect {effect: Effect::Hex, duration: 4, meta: None, tick: None}], Skill::HostilityI => vec![ConstructEffect {effect: Effect::Hostility, duration: 2, meta: Some(EffectMeta::Skill(Skill::HatredI)), tick: None}], Skill::HostilityII => vec![ConstructEffect {effect: Effect::Hostility, duration: 3, meta: Some(EffectMeta::Skill(Skill::HatredII)), tick: None}], Skill::HostilityIII => vec![ConstructEffect {effect: Effect::Hostility, duration: 4, meta: Some(EffectMeta::Skill(Skill::HatredIII)), tick: None}], Skill::HatredI => vec![ConstructEffect {effect: Effect::Hatred, duration: 5, meta: None, tick: None}], Skill::HatredII => vec![ConstructEffect {effect: Effect::Hatred, duration: 7, meta: None, tick: None}], Skill::HatredIII => vec![ConstructEffect {effect: Effect::Hatred, duration: 9, meta: None, tick: None}], Skill::ImpurityI => vec![ConstructEffect {effect: Effect::Impurity, duration: 2, meta: Some(EffectMeta::Multiplier(150)), tick: None }], Skill::ImpurityII => vec![ConstructEffect {effect: Effect::Impurity, duration: 3, meta: Some(EffectMeta::Multiplier(175)), tick: None }], Skill::ImpurityIII => vec![ConstructEffect {effect: Effect::Impurity, duration: 4, meta: Some(EffectMeta::Multiplier(225)), tick: None }], Skill::InvertI => vec![ConstructEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}], Skill::InvertII => vec![ConstructEffect {effect: Effect::Invert, duration: 3, meta: None, tick: None}], Skill::InvertIII => vec![ConstructEffect {effect: Effect::Invert, duration: 4, meta: None, tick: None}], Skill::ParryI => vec![ConstructEffect {effect: Effect::Parry, duration: 2, meta: Some(EffectMeta::Skill(Skill::RiposteI)), tick: None}], Skill::ParryII => vec![ConstructEffect {effect: Effect::Parry, duration: 2, meta: Some(EffectMeta::Skill(Skill::RiposteII)), tick: None}], Skill::ParryIII => vec![ConstructEffect {effect: Effect::Parry, duration: 2, meta: Some(EffectMeta::Skill(Skill::RiposteIII)), tick: None}], Skill::ReflectI => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], Skill::ReflectII => vec![ConstructEffect {effect: Effect::Reflect, duration: 2, meta: None, tick: None }], Skill::ReflectIII => vec![ConstructEffect {effect: Effect::Reflect, duration: 3, meta: None, tick: None }], Skill::ThrowI => 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::ThrowII => 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::ThrowIII => 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::RuinI => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], Skill::RuinII => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], Skill::RuinIII => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], Skill::ScatterI => vec![ConstructEffect {effect: Effect::Scatter, duration: 2, meta: None, tick: None}], Skill::ScatterII => vec![ConstructEffect {effect: Effect::Scatter, duration: 3, meta: None, tick: None}], Skill::ScatterIII => vec![ConstructEffect {effect: Effect::Scatter, duration: 4, meta: None, tick: None}], Skill::SilenceI => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}], Skill::SilenceII => vec![ConstructEffect {effect: Effect::Silence, duration: 3, meta: None, tick: None}], Skill::SilenceIII => vec![ConstructEffect {effect: Effect::Silence, duration: 4, meta: None, tick: None}], Skill::SiphonI => vec![ConstructEffect {effect: Effect::Siphon, duration: 2, meta: Some(EffectMeta::Skill(Skill::SiphonTickI)), tick: None}], Skill::SiphonII => vec![ConstructEffect {effect: Effect::Siphon, duration: 3, meta: Some(EffectMeta::Skill(Skill::SiphonTickII)), tick: None}], Skill::SiphonIII => vec![ConstructEffect {effect: Effect::Siphon, duration: 4, meta: Some(EffectMeta::Skill(Skill::SiphonTickIII)), tick: None}], Skill::SleepI => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], Skill::SleepII => vec![ConstructEffect {effect: Effect::Stun, duration: 3, meta: None, tick: None}], Skill::SleepIII => vec![ConstructEffect {effect: Effect::Stun, duration: 4, meta: None, tick: None}], Skill::SnareI => vec![ConstructEffect {effect: Effect::Snare, duration: 2, meta: None, tick: None}], Skill::SnareII => vec![ConstructEffect {effect: Effect::Snare, duration: 3, meta: None, tick: None}], Skill::SnareIII => vec![ConstructEffect {effect: Effect::Snare, duration: 4, meta: None, tick: None}], Skill::StrangleI => vec![ConstructEffect {effect: Effect::Strangle, duration: 2, meta: Some(EffectMeta::Skill(Skill::StrangleTickI)), tick: None}], Skill::StrangleII => vec![ConstructEffect {effect: Effect::Strangle, duration: 2, meta: Some(EffectMeta::Skill(Skill::StrangleTickII)), tick: None}], Skill::StrangleIII => vec![ConstructEffect {effect: Effect::Strangle, duration: 2, meta: Some(EffectMeta::Skill(Skill::StrangleTickIII)), tick: None}], Skill::Stun => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], Skill::TauntI => vec![ConstructEffect {effect: Effect::Taunt, duration: 2, meta: None, tick: None}], Skill::TauntII => vec![ConstructEffect {effect: Effect::Taunt, duration: 3, meta: None, tick: None}], Skill::TauntIII => vec![ConstructEffect {effect: Effect::Taunt, duration: 4, meta: None, tick: None}], Skill::TriageI => vec![ConstructEffect {effect: Effect::Triage, duration: 2, meta: Some(EffectMeta::Skill(Skill::TriageTickI)), tick: None}], Skill::TriageII => vec![ConstructEffect {effect: Effect::Triage, duration: 3, meta: Some(EffectMeta::Skill(Skill::TriageTickII)), tick: None}], Skill::TriageIII => vec![ConstructEffect {effect: Effect::Triage, duration: 4, meta: Some(EffectMeta::Skill(Skill::TriageTickIII)), tick: None}], //Unused // Skill::Injure => vec![ConstructEffect {effect: Effect::Injured, duration: 2, meta: None, tick: None }], _ => { panic!("{:?} no skill effect", self); }, } } pub fn base_cd(&self) -> Cooldown { match self { Skill::Attack => None, Skill::Debuff => Some(1), Skill::Buff => None, Skill::StrikeI => None, Skill::StrikeII => None, Skill::StrikeIII => None, Skill::Block => None, // reduce damage Skill::ParryI | Skill::ParryII | Skill::ParryIII => Some(2), // avoid all damage Skill::SnareI => Some(2), Skill::SnareII => Some(2), Skill::SnareIII => Some(2), Skill::Stun => Some(2), Skill::HealI => None, Skill::HealII => None, Skill::HealIII => None, Skill::TriageI => None, // hot Skill::TriageII => None, // hot Skill::TriageIII => None, // hot Skill::ThrowI => Some(1), // no damage stun, adds vulnerable Skill::ThrowII => Some(1), Skill::ThrowIII => Some(1), Skill::BlastI => None, Skill::BlastII => None, Skill::BlastIII => None, Skill::ChaosI => None, Skill::ChaosII => None, Skill::ChaosIII => None, Skill::AmplifyI => Some(1), Skill::AmplifyII => Some(1), Skill::AmplifyIII => Some(1), Skill::ImpurityI | Skill::ImpurityII | Skill::ImpurityIII => Some(3), Skill::InvertI => Some(2), Skill::InvertII => Some(2), Skill::InvertIII => Some(2), Skill::DecayI => Some(1), // dot Skill::DecayII => Some(1), Skill::DecayIII => Some(1), Skill::SiphonI | Skill::SiphonII | Skill::SiphonIII => None, Skill::CurseI => Some(1), Skill::CurseII => Some(1), Skill::CurseIII => Some(1), Skill::ScatterI => Some(2), Skill::ScatterII => Some(2), Skill::ScatterIII => Some(2), Skill::SilenceI => Some(3), Skill::SilenceII => Some(2), Skill::SilenceIII => Some(2), Skill::PurifyI => None, Skill::PurifyII => None, Skill::PurifyIII => None, Skill::PurgeI => None, Skill::PurgeII => None, Skill::PurgeIII => None, Skill::BanishI => Some(1), Skill::BanishII => Some(1), Skill::BanishIII => Some(1), Skill::HexI => Some(1), Skill::HexII => Some(2), Skill::HexIII => Some(2), Skill::HasteI => Some(2), Skill::HasteII => Some(2), Skill::HasteIII => Some(2), Skill::ReflectI => Some(2), Skill::ReflectII => Some(2), Skill::ReflectIII => Some(2), Skill::RechargeI => Some(2), Skill::RechargeII => Some(2), Skill::RechargeIII => Some(2), Skill::RuinI => Some(3), Skill::RuinII => Some(2), Skill::RuinIII => Some(2), Skill::SlayI => None, Skill::SlayII => None, Skill::SlayIII => None, Skill::SleepI => Some(3), Skill::SleepII => Some(3), Skill::SleepIII => Some(3), Skill::StrangleI => Some(2), Skill::StrangleII => Some(2), Skill::StrangleIII => Some(2), Skill::ClutchI => Some(1), Skill::ClutchII => Some(2), Skill::ClutchIII => Some(3), Skill::TauntI => Some(2), Skill::TauntII => Some(2), Skill::TauntIII => Some(2), // Skill::Injure => Some(2), Skill::CorruptI =>Some(1), Skill::CorruptII =>Some(1), Skill::CorruptIII =>Some(1), Skill::HostilityI | Skill::HostilityII | Skill::HostilityIII => Some(1), //----------- // Never cast directly //--------- // Trigger Skill::ImpureBlast | Skill::HasteStrike | Skill::RiposteI | Skill::RiposteII | Skill::RiposteIII | // parry Skill::CorruptionI | Skill::CorruptionII | Skill::CorruptionIII | Skill::HatredI | Skill::HatredII | Skill::HatredIII | // Ticks Skill::CorruptionTickI | Skill::CorruptionTickII | Skill::CorruptionTickIII | Skill::DecayTickI | Skill::DecayTickII | Skill::DecayTickIII | Skill::SiphonTickI | Skill::SiphonTickII | Skill::SiphonTickIII | Skill::StrangleTickI | Skill::StrangleTickII | Skill::StrangleTickIII | Skill::TriageTickI | Skill::TriageTickII | Skill::TriageTickIII => None, } } pub fn ko_castable(&self) -> bool { match self { Skill::CorruptionTickI | Skill::CorruptionTickII | Skill::CorruptionTickIII | Skill::DecayTickI | Skill::DecayTickII | Skill::DecayTickIII | Skill::SiphonTickI | Skill::SiphonTickII | Skill::SiphonTickIII | Skill::TriageTickI | Skill::TriageTickII | Skill::TriageTickIII => true, _ => false, } } pub fn is_tick(&self) -> bool { match self { Skill::CorruptionTickI | Skill::CorruptionTickII | Skill::CorruptionTickIII | Skill::DecayTickI | Skill::DecayTickII | Skill::DecayTickIII | Skill::SiphonTickI | Skill::SiphonTickII | Skill::SiphonTickIII | Skill::StrangleTickI | Skill::StrangleTickII | Skill::StrangleTickIII | Skill::TriageTickI | Skill::TriageTickII | Skill::TriageTickIII => true, _ => false, } } pub fn speed(&self) -> u64 { match self { Skill::StrikeI => Item::from(Skill::StrikeI).speed().pct(150), Skill::StrikeII => Skill::StrikeI.speed(), Skill::StrikeIII => Skill::StrikeI.speed(), Skill::SiphonTickI | Skill::SiphonTickII | Skill::SiphonTickIII => Skill::SiphonI.speed(), Skill::DecayTickI | Skill::DecayTickII | Skill::DecayTickIII => Skill::DecayI.speed(), Skill::TriageTickI | Skill::TriageTickII | Skill::TriageTickIII => Skill::TriageI.speed(), Skill::StrangleTickI | Skill::StrangleTickII | Skill::StrangleTickIII => Skill::StrangleI.speed(), Skill::CorruptionTickI | Skill::CorruptionTickII | Skill::CorruptionTickIII => Skill::CorruptI.speed(), _ => Item::from(*self).speed(), } } pub fn aoe(&self) -> bool { match self { Skill::RuinI | Skill::RuinII | Skill::RuinIII => true, _ => false, } } pub fn self_targeting(&self) -> bool { match self { Skill::Block | Skill::CorruptI | Skill::CorruptII | Skill::CorruptIII | Skill::ClutchI | Skill::ClutchII | Skill::ClutchIII | Skill::ParryI | Skill::ParryII | Skill::ParryIII => true, _ => false, } } pub fn defensive(&self) -> bool { let mut rng = thread_rng(); match self { Skill::AmplifyI | Skill::AmplifyII | Skill::AmplifyIII | Skill::Block | Skill::ClutchI | Skill::ClutchII | Skill::ClutchIII | Skill::CorruptI | Skill::CorruptII | Skill::CorruptIII | Skill::HasteI | Skill::HasteII | Skill::HasteIII | Skill::HealI | Skill::HealII | Skill::HealIII | Skill::HostilityI | Skill::HostilityII | Skill::HostilityIII | Skill::InvertI | Skill::InvertII | Skill::InvertIII | Skill::ParryI | Skill::ParryII | Skill::ParryIII | Skill::PurifyI | Skill::PurifyII | Skill::PurifyIII | Skill::RechargeI | Skill::RechargeII | Skill::RechargeIII | Skill::ReflectI | Skill::ReflectII | Skill::ReflectIII | Skill::ScatterI | Skill::ScatterII | Skill::ScatterIII | Skill::TriageI | Skill::TriageII | Skill::TriageIII => true, Skill::BanishI | Skill::BanishII | Skill::BanishIII => rng.gen_bool(0.5), _ => false, } } fn components(&self) -> Vec { let mut components = Item::from(*self).components(); components.sort_unstable(); return components; } 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 injure(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))); skill.effect().into_iter() .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); return results; } */ fn stun(source: &mut 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 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(LogStages::PostOnly))); return results; } fn clutch(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 taunt(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.recharge(skill, red_amount, 0))); let taunt = skill.effect()[0]; results.push(Resolution::new(source, target).event(target.add_effect(skill, taunt)).stages(LogStages::PostOnly)); return results; } fn throw(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(LogStages::PostOnly)); return results; } fn strangle(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; let tick_skill = match meta { Some(EffectMeta::Skill(s)) => s, _ => panic!("no strangle tick skill"), }; let strangle = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); results.push(Resolution::new(source, target).event(target.add_effect(skill, strangle))); let attacker_strangle = ConstructEffect::new(Effect::Strangling, duration); results.push(Resolution::new(source, source).event(source.add_effect(skill, attacker_strangle)).stages(LogStages::PostOnly)); return strangle_tick(source, target, results, tick_skill); } fn strangle_tick(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(LogStages::EndPost))); // remove immunity if target ko if target.is_ko() && !source.is_ko() { if let Some(i) = source.effects .iter() .position(|e| e.effect == Effect::Strangling) { source.effects.remove(i); results.push(Resolution::new(source, source) .event(Event::Removal { effect: Effect::Strangling, construct_effects: target.effects.clone() }) .stages(LogStages::PostOnly)); } else { error!("{:?}", results); println!("{:?}", results); panic!("no strangling on source"); } } 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])) .stages(LogStages::StartEnd)); 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 parry(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.recharge(skill, red_amount, 0)) .stages(LogStages::StartEnd)); results.push(Resolution::new(source, target) .event(target.add_effect(skill, skill.effect()[0])) .stages(LogStages::PostOnly)); return results; } fn riposte(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(LogStages::StartPost))); return results; } fn snare(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(LogStages::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()); 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(LogStages::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 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))); 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(LogStages::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(LogStages::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 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(LogStages::PostOnly)); 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(LogStages::EndPost))); return results; } // corrupt is the buff effect // when attacked it runs corruption and applies a debuff fn corrupt(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { let corrupt = skill.effect()[0]; results.push(Resolution::new(source, target).event(target.add_effect(skill, corrupt))); return results;; } fn corruption(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; let tick_skill = match meta { Some(EffectMeta::Skill(s)) => s, _ => panic!("no corruption tick skill"), }; let corruption = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); results.push(Resolution::new(source, target) .event(target.add_effect(skill, corruption)) .stages(LogStages::StartPost)); return corruption_tick(source, target, results, tick_skill); } fn corruption_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(LogStages::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(LogStages::PostOnly)); return results;; } fn hex(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 hostility(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 hatred(source: &mut Construct, target: &mut Construct, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions { let hatred = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); results.push(Resolution::new(source, target) .event(target.add_effect(reflect_skill, hatred)) .stages(LogStages::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 impurity(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()); results.push(Resolution::new(source, target) .event(target.recharge(skill, 0, blue_amount)) .stages(LogStages::PostOnly)); 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()); results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, blue_amount))); return results; } fn siphon(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { 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))); 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()); 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(LogStages::EndPost)); let heal = source.deal_green_damage(skill, amount); for h in heal { results.push(Resolution::new(source, source).event(h).stages(LogStages::PostOnly)); }; }, _ => results.push(Resolution::new(source, target).event(e).stages(LogStages::EndPost)), } } return results; } fn scatter(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { let blue_amount = source.blue_power().pct(skill.multiplier()); results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount))); let scatter = skill.effect()[0].set_meta(EffectMeta::ScatterTarget(target.id)); results.push(Resolution::new(source, target).event(source.add_effect(skill, scatter)).stages(LogStages::PostOnly)); return results; } fn scatter_hit(source: &Construct, target: &Construct, mut results: Resolutions, game: &mut Game, event: Event) -> Resolutions { match event { Event::Damage { amount, skill, mitigation: _, colour } => { let scatter = target.effects.iter().find(|e| e.effect == Effect::Scatter).unwrap(); if let Some(EffectMeta::ScatterTarget(scatter_target_id)) = scatter.meta { let mut scatter_target = game.construct_by_id(scatter_target_id).unwrap(); let res = match colour { Colour::Red => scatter_target.deal_red_damage(skill, amount), Colour::Blue => scatter_target.deal_blue_damage(skill, amount), Colour::Green => scatter_target.deal_green_damage(skill, amount), }; results.push(Resolution::new(target, scatter_target).event(Event::Skill { skill: Skill::ScatterI })); res.into_iter().for_each(|e| results.push(Resolution::new(&source, &scatter_target) .event(e).stages(LogStages::EndPost))); } else { panic!("not a scatter target {:?}", scatter); } return results; }, _ => panic!("{:?} scatter hit not damage event", event), } } 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(LogStages::PostOnly))); return results; } fn purge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, _skill: Skill) -> Resolutions { while let Some(i) = target.effects .iter() .position(|ce| ce.effect.category() == EffectCategory::Buff) { let ce = target.effects.remove(i); results.push(Resolution::new(source, target) .event(Event::Removal { effect: ce.effect, construct_effects: target.effects.clone() })); } 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 })); let amount = source.green_power().pct(skill.multiplier()); while let Some(i) = target.effects .iter() .position(|ce| ce.effect.category() == EffectCategory::Debuff) { let ce = target.effects.remove(i); results.push(Resolution::new(source, target) .event(Event::Removal { effect: ce.effect, construct_effects: target.effects.clone() }) .stages(LogStages::PostOnly)); target.deal_green_damage(skill, amount) .into_iter() .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::PostOnly))); } return results; } fn banish(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; } #[cfg(test)] mod tests { use skill::*; #[test] fn heal_test() { let mut x = Construct::new() .named(&"muji".to_string()) .learn(Skill::HealI); let mut y = Construct::new() .named(&"camel".to_string()) .learn(Skill::HealI); x.deal_red_damage(Skill::Attack, 5); heal(&mut y, &mut x, vec![], Skill::HealI); } #[test] fn decay_test() { let mut x = Construct::new() .named(&"muji".to_string()); let mut y = Construct::new() .named(&"camel".to_string()); decay(&mut x, &mut y, vec![], Skill::DecayI); 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 clutch_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 clutch(&mut y.clone(), &mut y, vec![], Skill::ClutchI); assert!(y.affected(Effect::Clutch)); let mut results = hex(&mut x, &mut y, vec![], Skill::HexI); let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); match event { Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Clutch)), _ => panic!("not immune cluthc"), }; let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); assert!(y.green_life() == 1); let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); match event { Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023), _ => panic!("not damage"), }; } /* #[test] fn injure_test() { let mut x = Construct::new() .named(&"muji".to_string()); let mut y = Construct::new() .named(&"camel".to_string()); resolve(Skill::Injure, &mut x, &mut y, vec![]); assert!(y.immune(Skill::HealI).is_some()); // resolutions = heal(&mut y.clone(), &mut y, resolutions); } */ #[test] fn invert_test() { let mut x = 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::InvertI); assert!(y.affected(Effect::Invert)); // heal should deal green damage heal(&mut x, &mut y, vec![], Skill::HealI); 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::ReflectI); assert!(y.affected(Effect::Reflect)); let mut results = vec![]; results = resolve(Skill::Attack, &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::Attack), _ => 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.green_life.reduce(512); let mut results = resolve(Skill::SiphonI, &mut x, &mut y, vec![]); assert!(y.affected(Effect::Siphon)); assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTickI.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::SiphonTickI.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::SiphonTickI.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::TriageI); 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::BlastI, 5); let mut results = recharge(&mut x, &mut y, vec![], Skill::RechargeI); 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::SilenceI); assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); assert!(x.disabled(Skill::SilenceI).is_some()); } #[test] fn amplify_test() { let mut x = Construct::new() .named(&"muji".to_string()); x.blue_power.force(50); amplify(&mut x.clone(), &mut x, vec![], Skill::AmplifyI); assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); assert_eq!(x.blue_power(), 75); } #[test] fn purify_test() { let mut x = Construct::new() .named(&"muji".to_string()); decay(&mut x.clone(), &mut x, vec![], Skill::DecayI); assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); purify(&mut x.clone(), &mut x, vec![], Skill::PurifyI); assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); } #[test] fn strangle_test() { let mut x = Construct::new() .named(&"muji".to_string()); let mut y = Construct::new() .named(&"pretaliation".to_string()); strangle(&mut x, &mut y, vec![], Skill::StrangleI); assert!(y.effects.iter().any(|e| e.effect == Effect::Strangle)); assert!(x.effects.iter().any(|e| e.effect == Effect::Strangling)); // ensure can't be removed purify(&mut x, &mut y, vec![], Skill::PurifyI); assert!(y.effects.iter().any(|e| e.effect == Effect::Strangle)); purge(&mut x.clone(), &mut x, vec![], Skill::PurgeI); assert!(x.effects.iter().any(|e| e.effect == Effect::Strangling)); } }