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 })); } 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 })); } 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) && skill.colours().contains(&Colour::Blue) { // 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::Hex| Skill::HexPlus | Skill::HexPlusPlus => hex(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: _ } => { if target.affected(Effect::Electric) { let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() .find(|e| e.effect == Effect::Electric).unwrap().clone(); match meta { Some(EffectMeta::Skill(s)) => { resolutions = electrocute(&mut target, &mut source, resolutions, s); }, _ => panic!("no electrify skill"), }; } if target.affected(Effect::Absorb) { 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"), }; } // beware that link doesn't cause any damage // because then applying it will proc this if target.affected(Effect::Link) { resolutions = link_hit(&source, &target, resolutions, game, event) } }, Event::Immunity { skill: _, immunity } => match immunity.contains(&Effect::Counter) { true => { let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() .find(|e| e.effect == Effect::Counter).unwrap().clone(); match meta { Some(EffectMeta::Skill(s)) => { resolutions = riposte(&mut target, &mut source, resolutions, s); }, _ => panic!("no counter skill"), }; }, false => (), }, _ => (), }; if target.is_ko() { resolutions.push(Resolution::new(&source, &target).event(Event::Ko())); 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: 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 EventStages { AllStages, // Anim Anim Anim StartEnd, // Anim Anim Skip StartPost, // Anim Skip Anim StartOnly, // Anim Skip Skip EndPost, // Skip Anim Anim EndOnly, // Skip Anim Skip PostOnly, // Skip Skip Anim None, // 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 } } #[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, AmplifyPlus, AmplifyPlusPlus, Banish, BanishPlus, BanishPlusPlus, Bash, BashPlus, BashPlusPlus, Blast, BlastPlus, BlastPlusPlus, Chaos, ChaosPlus, ChaosPlusPlus, Sustain, SustainPlus, SustainPlusPlus, Electrify, ElectrifyPlus, ElectrifyPlusPlus, Electrocute, ElectrocutePlus, ElectrocutePlusPlus, ElectrocuteTick, ElectrocuteTickPlus, ElectrocuteTickPlusPlus, Curse, CursePlus, CursePlusPlus, Decay, // dot DecayPlus, DecayPlusPlus, DecayTick, // dot DecayTickPlus, DecayTickPlusPlus, Haste, HastePlus, HastePlusPlus, HasteStrike, Heal, HealPlus, HealPlusPlus, Hex, HexPlus, HexPlusPlus, Absorption, AbsorptionPlus, AbsorptionPlusPlus, Absorb, AbsorbPlus, AbsorbPlusPlus, HybridBlast, Hybrid, HybridPlus, HybridPlusPlus, Invert, InvertPlus, InvertPlusPlus, Counter, // avoid all damage CounterPlus, CounterPlusPlus, Purge, PurgePlus, PurgePlusPlus, Purify, PurifyPlus, PurifyPlusPlus, Recharge, RechargePlus, RechargePlusPlus, Reflect, ReflectPlus, ReflectPlusPlus, CounterAttack, CounterAttackPlus, CounterAttackPlusPlus, Ruin, RuinPlus, RuinPlusPlus, Link, LinkPlus, LinkPlusPlus, Silence, SilencePlus, SilencePlusPlus, Siphon, SiphonPlus, SiphonPlusPlus, SiphonTick, SiphonTickPlus, SiphonTickPlusPlus, Slay, SlayPlus, SlayPlusPlus, Sleep, SleepPlus, SleepPlusPlus, Restrict, RestrictPlus, RestrictPlusPlus, Strike, StrikePlus, StrikePlusPlus, Intercept, InterceptPlus, InterceptPlusPlus, Break, // no damage stun, adds vulnerable BreakPlus, BreakPlusPlus, Triage, // hot TriagePlus, TriagePlusPlus, TriageTick, TriageTickPlus, TriageTickPlusPlus, } impl Skill { pub fn multiplier(&self) -> u64 { match self { // Attack Base Skill::Attack => 80, // Base Skill::Blast=> 110, // BB Skill::BlastPlus => 130, // BB Skill::BlastPlusPlus => 150, // BB Skill::Chaos=> 40, // BR Skill::ChaosPlus => 50, // BR Skill::ChaosPlusPlus => 60, // BR Skill::Heal=> 130, //GG Skill::HealPlus => 160, //GG Skill::HealPlusPlus => 200, //GG Skill::SiphonTick=> 40, // GB Skill::SiphonTickPlus => 70, Skill::SiphonTickPlusPlus => 110, Skill::Slay=> 70, // RG Skill::SlayPlus => 90, Skill::SlayPlusPlus => 120, Skill::Strike=> 90, //RR Skill::StrikePlus => 110, Skill::StrikePlusPlus => 140, // Block Base Skill::ElectrocuteTick=> 80, Skill::ElectrocuteTickPlus => 100, Skill::ElectrocuteTickPlusPlus => 130, Skill::Counter=> 110, Skill::CounterPlus => 145, Skill::CounterPlusPlus => 200, Skill::CounterAttack=> 70, Skill::CounterAttackPlus => 95, Skill::CounterAttackPlusPlus => 120, Skill::Purify=> 45, //Green dmg (heal) Skill::PurifyPlus => 70, Skill::PurifyPlusPlus => 105, Skill::Reflect=> 45, //restore blue life (heal) Skill::ReflectPlus => 70, Skill::ReflectPlusPlus => 100, Skill::Recharge=> 85, //restore red and blue life (heal) Skill::RechargePlus => 130, Skill::RechargePlusPlus => 200, // Stun Base Skill::Sleep=> 240, //Green dmg (heal) Skill::SleepPlus => 300, Skill::SleepPlusPlus => 400, Skill::Bash=> 65, Skill::BashPlus => 95, Skill::BashPlusPlus => 140, // Debuff Base Skill::DecayTick=> 25, 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=> 140, Skill::LinkPlus => 200, Skill::LinkPlusPlus => 300, 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: 1,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: 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::Electrify => vec![ConstructEffect {effect: Effect::Electric, duration: 2, meta: Some(EffectMeta::Skill(Skill::Electrocute)), tick: None}], Skill::ElectrifyPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 3, meta: Some(EffectMeta::Skill(Skill::ElectrocutePlus)), tick: None}], Skill::ElectrifyPlusPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 4, meta: Some(EffectMeta::Skill(Skill::ElectrocutePlusPlus)), tick: None}], Skill::Electrocute => vec![ConstructEffect {effect: Effect::Electrocute, duration: 3, meta: Some(EffectMeta::Skill(Skill::ElectrocuteTick)), tick: None}], Skill::ElectrocutePlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 4, meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickPlus)), tick: None}], Skill::ElectrocutePlusPlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 5, 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: 2, meta: None, tick: None }], Skill::SustainPlusPlus => vec![ConstructEffect {effect: Effect::Sustain, duration: 3, 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::Hex => vec![ConstructEffect {effect: Effect::Hex, duration: 2, meta: None, tick: None}], Skill::HexPlus => vec![ConstructEffect {effect: Effect::Hex, duration: 3, meta: None, tick: None}], Skill::HexPlusPlus => vec![ConstructEffect {effect: Effect::Hex, duration: 4, meta: None, 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: 5, meta: None, tick: None}], Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}], Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 9, 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: 2, meta: Some(EffectMeta::Skill(Skill::CounterAttack)), tick: None}], Skill::CounterPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 2, meta: Some(EffectMeta::Skill(Skill::CounterAttackPlus)), tick: None}], Skill::CounterPlusPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 2, meta: Some(EffectMeta::Skill(Skill::CounterAttackPlusPlus)), tick: None}], Skill::Reflect => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], Skill::ReflectPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 2, meta: None, tick: None }], Skill::ReflectPlusPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 3, 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: 1, meta: None, tick: None}], Skill::PurgePlus => vec![ConstructEffect {effect: Effect::Purge, duration: 2, meta: None, tick: None}], Skill::PurgePlusPlus => vec![ConstructEffect {effect: Effect::Purge, duration: 3, meta: None, tick: None}], Skill::Link => vec![ConstructEffect {effect: Effect::Link, duration: 2, meta: None, tick: None}], Skill::LinkPlus => vec![ConstructEffect {effect: Effect::Link, duration: 3, meta: None, tick: None}], Skill::LinkPlusPlus => vec![ConstructEffect {effect: Effect::Link, duration: 4, 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 => Some(2), // 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=> None, Skill::PurifyPlus => None, Skill::PurifyPlusPlus => None, Skill::Purge=> None, Skill::PurgePlus => None, Skill::PurgePlusPlus => None, Skill::Banish=> Some(1), Skill::BanishPlus => Some(1), Skill::BanishPlusPlus => Some(1), Skill::Hex=> Some(1), Skill::HexPlus => Some(2), Skill::HexPlusPlus => Some(2), Skill::Haste=> Some(2), Skill::HastePlus => Some(2), Skill::HastePlusPlus => Some(2), Skill::Reflect=> Some(2), Skill::ReflectPlus => Some(2), Skill::ReflectPlusPlus => Some(2), Skill::Recharge=> Some(2), Skill::RechargePlus => Some(2), Skill::RechargePlusPlus => Some(2), 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=> Some(1), Skill::SustainPlus => Some(2), Skill::SustainPlusPlus => Some(3), Skill::Intercept=> Some(2), Skill::InterceptPlus => Some(2), Skill::InterceptPlusPlus => Some(2), Skill::Electrify=>Some(1), Skill::ElectrifyPlus =>Some(1), Skill::ElectrifyPlusPlus =>Some(1), 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::Strike=> Item::from(Skill::Strike).speed().pct(150), Skill::StrikePlus => Skill::Strike.speed(), Skill::StrikePlusPlus => Skill::Strike.speed(), 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 self_targeting(&self) -> bool { match self { Skill::Block | Skill::Sustain| Skill::SustainPlus | Skill::SustainPlusPlus | Skill::Counter| Skill::CounterPlus | Skill::CounterPlusPlus => 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::Counter| Skill::CounterPlus | Skill::CounterPlusPlus | Skill::Purify| Skill::PurifyPlus | Skill::PurifyPlusPlus | Skill::Recharge| Skill::RechargePlus | Skill::RechargePlusPlus | Skill::Reflect| Skill::ReflectPlus | Skill::ReflectPlusPlus | Skill::Link| Skill::LinkPlus | Skill::LinkPlusPlus | 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 { skill.effect().into_iter() .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); return results; } fn intercept(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 intercept = skill.effect()[0]; results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept)).stages(EventStages::PostOnly)); 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])) .stages(EventStages::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 counter(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(EventStages::StartEnd)); results.push(Resolution::new(source, target) .event(target.add_effect(skill, skill.effect()[0])) .stages(EventStages::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(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()); 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 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(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 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)); 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 { 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 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::EndPost)); 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 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 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)); 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()); results.push(Resolution::new(source, target) .event(target.recharge(skill, 0, blue_amount)) .stages(EventStages::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(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 blue_amount = source.blue_power().pct(skill.multiplier()); results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount))); let link = skill.effect()[0].set_meta(EffectMeta::LinkTarget(target.id)); results.push(Resolution::new(source, target).event(source.add_effect(skill, link)).stages(EventStages::PostOnly)); return results; } fn link_hit(source: &Construct, target: &Construct, mut results: Resolutions, game: &mut Game, event: Event) -> Resolutions { match event { Event::Damage { amount, skill, mitigation: _, colour } => { let link = target.effects.iter().find(|e| e.effect == Effect::Link).unwrap(); if let Some(EffectMeta::LinkTarget(link_target_id)) = link.meta { let mut link_target = game.construct_by_id(link_target_id).unwrap(); let res = match colour { Colour::Red => link_target.deal_red_damage(skill, amount), Colour::Blue => link_target.deal_blue_damage(skill, amount), Colour::Green => link_target.deal_green_damage(skill, amount), }; results.push(Resolution::new(target, link_target).event(Event::Skill { skill: Skill::Link})); res.into_iter().for_each(|e| results.push(Resolution::new(&source, &link_target) .event(e).stages(EventStages::EndPost))); } else { panic!("not a link target {:?}", link); } return results; }, _ => panic!("{:?} link 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(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 })); 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() })); } let mut turns = 0; for cs in target.skills.iter_mut() { if Effect::Purge.disables_skill(cs.skill) { turns += 1; } } if turns > 0 { let mut effect = skill.effect()[0]; effect.duration = effect.duration * turns; results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); } 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| { 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(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::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 sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain); assert!(y.affected(Effect::Sustain)); let mut results = hex(&mut x, &mut y, vec![], Skill::Hex); 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.green_life.reduce(512); let mut results = resolve(Skill::Siphon, &mut x, &mut y, vec![]); assert!(y.affected(Effect::Siphon)); assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()))); let Resolution { source: _, target: _, event, 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())), _ => 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())); 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()); } }