mnml/server/src/skill.rs
2019-07-09 12:40:18 +10:00

2029 lines
74 KiB
Rust

use rand::{thread_rng, Rng};
use uuid::Uuid;
use util::{IntPct};
use construct::{Construct, ConstructEffect, EffectMeta};
use item::{Item};
use game::{Game};
use effect::{Effect, Colour, Cooldown};
pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions {
let mut resolutions = vec![];
let mut a = Construct::new();
a.id = a_id;
let mut b = Construct::new();
b.id = b_id;
if skill.aoe() { // Send an aoe skill event for anims
resolutions.push(Resolution::new(&a, &b).event(Event::AoeSkill { skill }));
}
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<Resolution>) -> Resolutions {
if let Some(disable) = source.disabled(skill) {
resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }));
return resolutions;
}
if target.is_ko() {
resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }));
return resolutions;
}
if target.affected(Effect::Reflect) && 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::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::Hybrid) {
match skill {
Skill::BlastI |
Skill::ChaosI |
Skill::SiphonI => {
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::AmplifyI |
Skill::AmplifyII |
Skill::AmplifyIII => amplify(source, target, resolutions, skill),
Skill::BanishI |
Skill::BanishII |
Skill::BanishIII => banish(source, target, resolutions, skill),
Skill::BashI |
Skill::BashII |
Skill::BashIII => bash(source, target, resolutions, skill),
Skill::BlastI |
Skill::BlastII |
Skill::BlastIII => blast(source, target, resolutions, skill),
Skill::ChaosI |
Skill::ChaosII |
Skill::ChaosIII => chaos(source, target, resolutions, skill),
Skill::SustainI |
Skill::SustainII |
Skill::SustainIII => sustain(source, target, resolutions, skill),
Skill::ElectrifyI |
Skill::ElectrifyII |
Skill::ElectrifyIII => electrify(source, target, resolutions, skill),
Skill::ElectrocuteTickI |
Skill::ElectrocuteTickII |
Skill::ElectrocuteTickIII => electrocute_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::AbsorbI |
Skill::AbsorbII |
Skill::AbsorbIII => absorb(source, target, resolutions, skill),
Skill::HybridI |
Skill::HybridII |
Skill::HybridIII => hybrid(source, target, resolutions, skill),
Skill::InvertI |
Skill::InvertII |
Skill::InvertIII => invert(source, target, resolutions, skill),
Skill::CounterI |
Skill::CounterII |
Skill::CounterIII => counter(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::LinkI |
Skill::LinkII |
Skill::LinkIII => link(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::RestrictI |
Skill::RestrictII |
Skill::RestrictIII => restrict(source, target, resolutions, skill),
Skill::StrikeI |
Skill::StrikeII |
Skill::StrikeIII => strike(source, target, resolutions, skill),
Skill::InterceptI |
Skill::InterceptII |
Skill::InterceptIII => intercept(source, target, resolutions, skill),
Skill::BreakI |
Skill::BreakII |
Skill::BreakIII => break_(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::ElectrocuteI |
Skill::ElectrocuteII |
Skill::ElectrocuteIII => panic!("should only trigger from electrify hit"),
Skill::HasteStrike => panic!("should only trigger from haste"),
Skill::AbsorptionI |
Skill::AbsorptionII |
Skill::AbsorptionIII => panic!("should only trigger from absorb"),
Skill::HybridBlast => panic!("should only trigger from hybrid"),
Skill::CounterAttackI |
Skill::CounterAttackII |
Skill::CounterAttackIII => 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 target, &mut source, 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 => (),
},
_ => (),
};
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<Effect>;
pub type Immunity = Vec<Effect>;
// used to show the progress of a construct
// while the resolutions are animating
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct EventConstruct {
pub id: Uuid,
pub red: u64,
pub green: u64,
pub blue: u64,
}
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub enum 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 },
AoeSkill { skill: Skill },
Skill { skill: Skill },
Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec<ConstructEffect> },
Removal { effect: Effect, construct_effects: Vec<ConstructEffect> },
TargetKo { skill: Skill },
// skill not necessary but makes it neater as all events are arrays in js
Ko (),
Forfeit (),
Incomplete,
// not used
Evasion { skill: Skill, evasion_rating: u64 },
}
pub type Resolutions = Vec<Resolution>;
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
pub enum Skill {
Attack,
Debuff,
Buff,
Block, // reduce damage
Stun,
// Boost -- sounds nice
// Evade, // actively evade
// Nightmare,
// Sleep,
AmplifyI,
AmplifyII,
AmplifyIII,
BanishI,
BanishII,
BanishIII,
BashI,
BashII,
BashIII,
BlastI,
BlastII,
BlastIII,
ChaosI,
ChaosII,
ChaosIII,
SustainI,
SustainII,
SustainIII,
ElectrifyI,
ElectrifyII,
ElectrifyIII,
ElectrocuteI,
ElectrocuteII,
ElectrocuteIII,
ElectrocuteTickI,
ElectrocuteTickII,
ElectrocuteTickIII,
CurseI,
CurseII,
CurseIII,
DecayI, // dot
DecayII,
DecayIII,
DecayTickI, // dot
DecayTickII,
DecayTickIII,
HasteI,
HasteII,
HasteIII,
HasteStrike,
HealI,
HealII,
HealIII,
HexI,
HexII,
HexIII,
AbsorptionI,
AbsorptionII,
AbsorptionIII,
AbsorbI,
AbsorbII,
AbsorbIII,
HybridBlast,
HybridI,
HybridII,
HybridIII,
InvertI,
InvertII,
InvertIII,
CounterI, // avoid all damage
CounterII,
CounterIII,
PurgeI,
PurgeII,
PurgeIII,
PurifyI,
PurifyII,
PurifyIII,
RechargeI,
RechargeII,
RechargeIII,
ReflectI,
ReflectII,
ReflectIII,
CounterAttackI,
CounterAttackII,
CounterAttackIII,
RuinI,
RuinII,
RuinIII,
LinkI,
LinkII,
LinkIII,
SilenceI,
SilenceII,
SilenceIII,
SiphonI,
SiphonII,
SiphonIII,
SiphonTickI,
SiphonTickII,
SiphonTickIII,
SlayI,
SlayII,
SlayIII,
SleepI,
SleepII,
SleepIII,
RestrictI,
RestrictII,
RestrictIII,
StrikeI,
StrikeII,
StrikeIII,
InterceptI,
InterceptII,
InterceptIII,
BreakI, // no damage stun, adds vulnerable
BreakII,
BreakIII,
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::ElectrocuteTickI => 80,
Skill::ElectrocuteTickII => 100,
Skill::ElectrocuteTickIII => 130,
Skill::CounterI => 110,
Skill::CounterII => 145,
Skill::CounterIII => 200,
Skill::CounterAttackI => 70,
Skill::CounterAttackII => 95,
Skill::CounterAttackIII => 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::BashI => 65,
Skill::BashII => 95,
Skill::BashIII => 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::RestrictI => 40, // Deals more per red skill on target
Skill::RestrictII => 65,
Skill::RestrictIII => 100,
// Buff base
Skill::HybridBlast => 25,
Skill::HasteStrike => 30,
Skill::LinkI => 140,
Skill::LinkII => 200,
Skill::LinkIII => 300,
Skill::InterceptI => 80,
Skill::InterceptII => 110,
Skill::InterceptIII => 150,
Skill::TriageTickI => 75,
Skill::TriageTickII => 110,
Skill::TriageTickIII => 140,
_ => 100,
}
}
pub fn effect(&self) -> Vec<ConstructEffect> {
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::ElectrifyI => vec![ConstructEffect {effect: Effect::Electric, duration: 2,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteI)), tick: None}],
Skill::ElectrifyII => vec![ConstructEffect {effect: Effect::Electric, duration: 3,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteII)), tick: None}],
Skill::ElectrifyIII => vec![ConstructEffect {effect: Effect::Electric, duration: 4,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteIII)), tick: None}],
Skill::ElectrocuteI => vec![ConstructEffect {effect: Effect::Electrocute, duration: 3,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickI)), tick: None}],
Skill::ElectrocuteII => vec![ConstructEffect {effect: Effect::Electrocute, duration: 4,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickII)), tick: None}],
Skill::ElectrocuteIII => vec![ConstructEffect {effect: Effect::Electrocute, duration: 5,
meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickIII)), tick: None}],
Skill::SustainI => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }],
Skill::SustainII => vec![ConstructEffect {effect: Effect::Sustain, duration: 2, meta: None, tick: None }],
Skill::SustainIII => vec![ConstructEffect {effect: Effect::Sustain, 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::AbsorbI => vec![ConstructEffect {effect: Effect::Absorb, duration: 2,
meta: Some(EffectMeta::Skill(Skill::AbsorptionI)), tick: None}],
Skill::AbsorbII => vec![ConstructEffect {effect: Effect::Absorb, duration: 3,
meta: Some(EffectMeta::Skill(Skill::AbsorptionII)), tick: None}],
Skill::AbsorbIII => vec![ConstructEffect {effect: Effect::Absorb, duration: 4,
meta: Some(EffectMeta::Skill(Skill::AbsorptionIII)), tick: None}],
Skill::AbsorptionI => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}],
Skill::AbsorptionII => vec![ConstructEffect {effect: Effect::Absorption, duration: 7, meta: None, tick: None}],
Skill::AbsorptionIII => vec![ConstructEffect {effect: Effect::Absorption, duration: 9, meta: None, tick: None}],
Skill::HybridI => vec![ConstructEffect {effect: Effect::Hybrid, duration: 2,
meta: Some(EffectMeta::Multiplier(150)), tick: None }],
Skill::HybridII => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3,
meta: Some(EffectMeta::Multiplier(175)), tick: None }],
Skill::HybridIII => vec![ConstructEffect {effect: Effect::Hybrid, 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::CounterI => vec![ConstructEffect {effect: Effect::Counter, duration: 2,
meta: Some(EffectMeta::Skill(Skill::CounterAttackI)), tick: None}],
Skill::CounterII => vec![ConstructEffect {effect: Effect::Counter, duration: 2,
meta: Some(EffectMeta::Skill(Skill::CounterAttackII)), tick: None}],
Skill::CounterIII => vec![ConstructEffect {effect: Effect::Counter, duration: 2,
meta: Some(EffectMeta::Skill(Skill::CounterAttackIII)), 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::BreakI => 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::BreakII => 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::BreakIII => 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::PurgeI => vec![ConstructEffect {effect: Effect::Purge, duration: 1, meta: None, tick: None}],
Skill::PurgeII => vec![ConstructEffect {effect: Effect::Purge, duration: 2, meta: None, tick: None}],
Skill::PurgeIII => vec![ConstructEffect {effect: Effect::Purge, duration: 3, meta: None, tick: None}],
Skill::LinkI => vec![ConstructEffect {effect: Effect::Link, duration: 2, meta: None, tick: None}],
Skill::LinkII => vec![ConstructEffect {effect: Effect::Link, duration: 3, meta: None, tick: None}],
Skill::LinkIII => vec![ConstructEffect {effect: Effect::Link, 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::RestrictI => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}],
Skill::RestrictII => vec![ConstructEffect {effect: Effect::Restrict, duration: 3, meta: None, tick: None}],
Skill::RestrictIII => vec![ConstructEffect {effect: Effect::Restrict, duration: 4, meta: None, tick: None}],
Skill::BashI => vec![ConstructEffect {effect: Effect::Stun, duration: 2,
meta: Some(EffectMeta::Skill(Skill::BashI)), tick: None}],
Skill::BashII => vec![ConstructEffect {effect: Effect::Stun, duration: 2,
meta: Some(EffectMeta::Skill(Skill::BashII)), tick: None}],
Skill::BashIII => vec![ConstructEffect {effect: Effect::Stun, duration: 2,
meta: Some(EffectMeta::Skill(Skill::BashIII)), tick: None}],
Skill::Stun => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}],
Skill::InterceptI => vec![ConstructEffect {effect: Effect::Intercept, duration: 2, meta: None, tick: None}],
Skill::InterceptII => vec![ConstructEffect {effect: Effect::Intercept, duration: 3, meta: None, tick: None}],
Skill::InterceptIII => vec![ConstructEffect {effect: Effect::Intercept, 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}],
_ => {
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::CounterI |
Skill::CounterII |
Skill::CounterIII => Some(2), // avoid all damage
Skill::RestrictI => Some(2),
Skill::RestrictII => Some(2),
Skill::RestrictIII => Some(2),
Skill::Stun => Some(2),
Skill::BashI => Some(2),
Skill::BashII => Some(2),
Skill::BashIII => Some(2),
Skill::HealI => None,
Skill::HealII => None,
Skill::HealIII => None,
Skill::TriageI => None, // hot
Skill::TriageII => None, // hot
Skill::TriageIII => None, // hot
Skill::BreakI => Some(1), // no damage stun, adds vulnerable
Skill::BreakII => Some(1),
Skill::BreakIII => 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::HybridI |
Skill::HybridII |
Skill::HybridIII => 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::LinkI => Some(2),
Skill::LinkII => Some(2),
Skill::LinkIII => 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::SustainI => Some(1),
Skill::SustainII => Some(2),
Skill::SustainIII => Some(3),
Skill::InterceptI => Some(2),
Skill::InterceptII => Some(2),
Skill::InterceptIII => Some(2),
Skill::ElectrifyI =>Some(1),
Skill::ElectrifyII =>Some(1),
Skill::ElectrifyIII =>Some(1),
Skill::AbsorbI |
Skill::AbsorbII |
Skill::AbsorbIII => Some(1),
//-----------
// Never cast directly
//---------
// Trigger
Skill::HybridBlast |
Skill::HasteStrike |
Skill::CounterAttackI |
Skill::CounterAttackII |
Skill::CounterAttackIII | // counter
Skill::ElectrocuteI |
Skill::ElectrocuteII |
Skill::ElectrocuteIII |
Skill::AbsorptionI |
Skill::AbsorptionII |
Skill::AbsorptionIII |
// Ticks
Skill::ElectrocuteTickI |
Skill::ElectrocuteTickII |
Skill::ElectrocuteTickIII |
Skill::DecayTickI |
Skill::DecayTickII |
Skill::DecayTickIII |
Skill::SiphonTickI |
Skill::SiphonTickII |
Skill::SiphonTickIII |
Skill::TriageTickI |
Skill::TriageTickII |
Skill::TriageTickIII => None,
}
}
pub fn ko_castable(&self) -> bool {
match self {
Skill::ElectrocuteTickI |
Skill::ElectrocuteTickII |
Skill::ElectrocuteTickIII |
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::ElectrocuteTickI |
Skill::ElectrocuteTickII |
Skill::ElectrocuteTickIII |
Skill::DecayTickI |
Skill::DecayTickII |
Skill::DecayTickIII |
Skill::SiphonTickI |
Skill::SiphonTickII |
Skill::SiphonTickIII |
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::ElectrocuteTickI |
Skill::ElectrocuteTickII |
Skill::ElectrocuteTickIII => Skill::ElectrifyI.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::SustainI |
Skill::SustainII |
Skill::SustainIII |
Skill::CounterI |
Skill::CounterII |
Skill::CounterIII => true,
_ => false,
}
}
pub fn defensive(&self) -> bool {
let mut rng = thread_rng();
match self {
Skill::AmplifyI |
Skill::AmplifyII |
Skill::AmplifyIII |
Skill::Block |
Skill::SustainI |
Skill::SustainII |
Skill::SustainIII |
Skill::ElectrifyI |
Skill::ElectrifyII |
Skill::ElectrifyIII |
Skill::HasteI |
Skill::HasteII |
Skill::HasteIII |
Skill::HealI |
Skill::HealII |
Skill::HealIII |
Skill::AbsorbI |
Skill::AbsorbII |
Skill::AbsorbIII |
Skill::InvertI |
Skill::InvertII |
Skill::InvertIII |
Skill::CounterI |
Skill::CounterII |
Skill::CounterIII |
Skill::PurifyI |
Skill::PurifyII |
Skill::PurifyIII |
Skill::RechargeI |
Skill::RechargeII |
Skill::RechargeIII |
Skill::ReflectI |
Skill::ReflectII |
Skill::ReflectIII |
Skill::LinkI |
Skill::LinkII |
Skill::LinkIII |
Skill::TriageI |
Skill::TriageII |
Skill::TriageIII => true,
Skill::BanishI |
Skill::BanishII |
Skill::BanishIII => rng.gen_bool(0.5),
_ => false,
}
}
fn components(&self) -> Vec<Item> {
let mut components = Item::from(*self).components();
components.sort_unstable();
return components;
}
pub fn colours(&self) -> Vec<Colour> {
let mut components = self.components();
let colour_items = [Item::Red, Item::Green, Item::Blue];
components.dedup();
return components.iter()
.filter(|i| colour_items.contains(i))
.map(|i| i.into_colour())
.collect::<Vec<Colour>>();
}
fn base(&self) -> Skill {
let bases = [Item::Attack, Item::Stun, Item::Buff, Item::Debuff, Item::Block];
match self.components()
.iter()
.find(|i| bases.contains(i)) {
Some(i) => i.into_skill().unwrap(),
None => panic!("{:?} has no base item", self),
}
}
}
fn attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_power().pct(skill.multiplier());
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn strike(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
let amount = source.red_power().pct(skill.multiplier());
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
return results;
}
fn stun(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
return results;
}
fn bash(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions {
skill.effect().into_iter()
.for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e)))));
if results.iter().any(|r| match r.event {
Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ }
=> effect == Effect::Stun && skill == effect_skill,
_ => false,
}) {
let mut cds = 0;
for cs in target.skills.iter_mut() {
if cs.skill.base_cd().is_some() {
cs.cd = match cs.cd {
None => Some(1),
Some(i) => Some(i + 1),
};
cds += 1;
}
}
let amount = source.red_power().pct(skill.multiplier().pct(100 + 45u64.saturating_mul(cds)));
target.deal_red_damage(skill, amount)
.into_iter()
.for_each(|e| results.push(Resolution::new(source, target).event(e).stages(LogStages::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(LogStages::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(LogStages::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(LogStages::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(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 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(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 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(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;
}
// 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(LogStages::StartPost));
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(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 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(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 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(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 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(LogStages::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::LinkI }));
res.into_iter().for_each(|e| results.push(Resolution::new(&source, &link_target)
.event(e).stages(LogStages::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(LogStages::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)));
}
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(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 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::SustainI);
assert!(y.affected(Effect::Sustain));
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::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::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::BlastI, &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::BlastI),
_ => 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 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::BashI);
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::HealI)
.learn(Skill::HealII);
purge(&mut x, &mut y, vec![], Skill::PurgeI);
// 2 turns at lvl 1
assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2));
assert!(y.disabled(Skill::HealI).is_some());
}
}