1713 lines
58 KiB
Rust
1713 lines
58 KiB
Rust
use rand::{thread_rng, Rng};
|
|
use uuid::Uuid;
|
|
|
|
use util::{IntPct};
|
|
use cryp::{Cryp, CrypEffect, EffectMeta, Stat};
|
|
use item::{Item};
|
|
|
|
use game::{Game};
|
|
|
|
pub fn resolution_steps(cast: &Cast, game: &mut Game) -> Resolutions {
|
|
let mut resolutions = vec![];
|
|
|
|
resolutions = pre_resolve(cast, game, resolutions);
|
|
|
|
return resolutions;
|
|
}
|
|
|
|
pub fn pre_resolve(cast: &Cast, game: &mut Game, mut resolutions: Resolutions) -> Resolutions {
|
|
let skill = cast.skill;
|
|
let source = game.cryp_by_id(cast.source_cryp_id).unwrap().clone();
|
|
let targets = game.get_targets(cast.skill, &source, cast.target_cryp_id);
|
|
|
|
if skill.aoe() { // Send an aoe skill event for anims
|
|
resolutions.push(Resolution::new(&source, &game.cryp_by_id(cast.target_cryp_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.cryp_by_id(cast.source_cryp_id).unwrap().clone();
|
|
let mut target = game.cryp_by_id(target_id).unwrap().clone();
|
|
|
|
// bail out on ticks that have been removed
|
|
if cast.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_cryp(&mut source);
|
|
game.update_cryp(&mut target);
|
|
|
|
// do additional steps
|
|
resolutions = post_resolve(cast.skill, game, resolutions);
|
|
}
|
|
|
|
return resolutions;
|
|
}
|
|
|
|
pub fn resolve(skill: Skill, source: &mut Cryp, target: &mut Cryp, 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) {
|
|
// guard against overflow
|
|
if source.affected(Effect::Reflect) {
|
|
return resolutions;
|
|
}
|
|
resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill }));
|
|
return resolve(skill, target, source, resolutions);
|
|
}
|
|
|
|
if source.affected(Effect::Haste) {
|
|
match skill {
|
|
Skill::Attack |
|
|
Skill::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::Impurity) {
|
|
match skill {
|
|
Skill::Blast |
|
|
Skill::Chaos |
|
|
Skill::Siphon => {
|
|
let amount = source.green_damage().pct(Skill::ImpureBlast.multiplier());
|
|
target.deal_blue_damage(Skill::ImpureBlast, amount)
|
|
.into_iter()
|
|
.for_each(|e| resolutions.push(Resolution::new(source, target).event(e)));
|
|
},
|
|
_ => (),
|
|
}
|
|
}
|
|
|
|
// match self.category() == Category::Red {
|
|
// true => {
|
|
// if let Some(evasion) = target.evade(*self) {
|
|
// resolutions.push(evasion);
|
|
// return Event;
|
|
// }
|
|
// },
|
|
// false => (),
|
|
// }
|
|
|
|
resolutions = match skill {
|
|
Skill::Amplify => amplify(source, target, resolutions, skill), // increase magic damage
|
|
Skill::Attack => attack(source, target, resolutions, skill),
|
|
Skill::Banish => banish(source, target, resolutions, skill), // TODO prevent all actions
|
|
Skill::Blast => blast(source, target, resolutions, skill),
|
|
Skill::Block => block(source, target, resolutions, skill),
|
|
Skill::Chaos => chaos(source, target, resolutions, skill),
|
|
Skill::Clutch => clutch(source, target, resolutions, skill),
|
|
Skill::Corrupt => corrupt(source, target, resolutions, skill),
|
|
Skill::CorruptionTick => corruption_tick(source, target, resolutions, skill),
|
|
Skill::Curse => curse(source, target, resolutions, skill),
|
|
Skill::Debuff => debuff(source, target, resolutions, skill), // speed slow
|
|
Skill::Decay => decay(source, target, resolutions, skill), // dot
|
|
Skill::DecayTick => decay_tick(source, target, resolutions, skill), // dot
|
|
Skill::Haste => haste(source, target, resolutions, skill), // speed slow
|
|
Skill::HasteStrike => panic!("haste strike should not be caste"),
|
|
Skill::Heal => heal(source, target, resolutions, skill),
|
|
Skill::Hex => hex(source, target, resolutions, skill),
|
|
Skill::Hostility => hostility(source, target, resolutions, skill),
|
|
Skill::Impurity => impurity(source, target, resolutions, skill),
|
|
Skill::ImpureBlast => panic!("impure blast should not be caste"),
|
|
Skill::Invert => invert(source, target, resolutions, skill),
|
|
Skill::Injure => injure(source, target, resolutions, skill),
|
|
Skill::Parry => parry(source, target, resolutions, skill),
|
|
Skill::Purge => purge(source, target, resolutions, skill), // dispel all buffs
|
|
Skill::Purify => purify(source, target, resolutions, skill), // dispel all debuffs
|
|
Skill::Recharge => recharge(source, target, resolutions, skill), // target is immune to magic damage and fx
|
|
Skill::Reflect => reflect(source, target, resolutions, skill),
|
|
Skill::Riposte => panic!("riposte should not be caste"),
|
|
Skill::Ruin => ruin(source, target, resolutions, skill),
|
|
Skill::Scatter => scatter(source, target, resolutions, skill), // target is immune to magic damage and fx
|
|
Skill::Silence => silence(source, target, resolutions, skill), // target cannot cast spells
|
|
Skill::Siphon => siphon(source, target, resolutions, skill),
|
|
Skill::SiphonTick => siphon_tick(source, target, resolutions, skill), // hot
|
|
Skill::Slay => slay(source, target, resolutions, skill), // hybrid dmg self heal
|
|
Skill::Sleep => sleep(source, target, resolutions, skill), // speed slow
|
|
Skill::Snare => snare(source, target, resolutions, skill),
|
|
Skill::Strangle => strangle(source, target, resolutions, skill),
|
|
Skill::StrangleTick => strangle_tick(source, target, resolutions, skill),
|
|
|
|
Skill::Strike => strike(source, target, resolutions, skill),
|
|
Skill::StrikeII => strike(source, target, resolutions, skill),
|
|
Skill::StrikeIII => strike(source, target, resolutions, skill),
|
|
|
|
Skill::Stun => stun(source, target, resolutions, skill),
|
|
Skill::Taunt => taunt(source, target, resolutions, skill),
|
|
Skill::Throw => throw(source, target, resolutions, skill), // no damage stun, adds vulnerable
|
|
Skill::Triage => triage(source, target, resolutions, skill), // hot
|
|
Skill::TriageTick => triage_tick(source, target, resolutions, skill), // hot
|
|
|
|
// -----------------
|
|
// Test
|
|
// -----------------
|
|
Skill::TestAttack => attack(source, target, resolutions, skill),
|
|
Skill::TestHeal => heal(source, target, resolutions, skill),
|
|
Skill::TestTouch => touch(source, target, resolutions, skill),
|
|
Skill::TestStun => stun(source, target, resolutions, Skill::Stun),
|
|
Skill::TestBlock => block(source, target, resolutions, Skill::Block),
|
|
Skill::TestParry => parry(source, target, resolutions, Skill::Parry),
|
|
Skill::TestSiphon => siphon(source, target, resolutions, Skill::Siphon),
|
|
};
|
|
|
|
return resolutions;
|
|
}
|
|
|
|
fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions {
|
|
for Resolution { source, target, event } in resolutions.clone() {
|
|
let mut source = game.cryp_by_id(source.id).unwrap().clone();
|
|
let mut target = game.cryp_by_id(target.id).unwrap().clone();
|
|
|
|
match event {
|
|
Event::Damage { amount, skill, mitigation: _, colour: _ } => {
|
|
if target.affected(Effect::Corrupt) {
|
|
resolutions = corruption(&mut target, &mut source, resolutions, Skill::Corrupt);
|
|
}
|
|
|
|
if target.affected(Effect::Hostility) {
|
|
resolutions = hatred(&mut source, &mut target, resolutions, skill, amount, Skill::Hostility);
|
|
}
|
|
|
|
// beware that scatter doesn't cause any damage
|
|
// because then applying it will proc this
|
|
if target.affected(Effect::Scatter) {
|
|
resolutions = scatter_hit(&source, &target, resolutions, game, event)
|
|
}
|
|
},
|
|
|
|
Event::Immunity { skill: _, immunity } => match immunity.contains(&Effect::Parry) {
|
|
true => {
|
|
resolutions = riposte(&mut target, &mut source, resolutions, Skill::Riposte);
|
|
}
|
|
false => (),
|
|
},
|
|
_ => (),
|
|
};
|
|
|
|
game.update_cryp(&mut source);
|
|
game.update_cryp(&mut target);
|
|
};
|
|
|
|
return resolutions;
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
pub struct Cast {
|
|
pub id: Uuid,
|
|
pub source_player_id: Uuid,
|
|
pub source_cryp_id: Uuid,
|
|
pub target_cryp_id: Uuid,
|
|
pub skill: Skill,
|
|
pub speed: u64,
|
|
pub is_tick: bool,
|
|
}
|
|
|
|
impl Cast {
|
|
pub fn new(source_cryp_id: Uuid, source_player_id: Uuid, target_cryp_id: Uuid, skill: Skill) -> Cast {
|
|
return Cast {
|
|
id: Uuid::new_v4(),
|
|
source_cryp_id,
|
|
source_player_id,
|
|
target_cryp_id,
|
|
skill,
|
|
speed: 0,
|
|
is_tick: false,
|
|
};
|
|
}
|
|
|
|
pub fn new_tick(source: &mut Cryp, target: &mut Cryp, skill: Skill) -> Cast {
|
|
Cast {
|
|
id: Uuid::new_v4(),
|
|
source_cryp_id: source.id,
|
|
source_player_id: source.account,
|
|
target_cryp_id: target.id,
|
|
skill,
|
|
speed: 0,
|
|
is_tick: true
|
|
}
|
|
}
|
|
|
|
pub fn used_cooldown(&self) -> bool {
|
|
return self.skill.base_cd().is_some();
|
|
}
|
|
}
|
|
|
|
pub type Disable = Vec<Effect>;
|
|
pub type Immunity = Vec<Effect>;
|
|
|
|
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
|
|
pub struct LogCryp {
|
|
pub id: Uuid,
|
|
pub name: String,
|
|
}
|
|
|
|
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
|
|
pub struct Resolution {
|
|
pub source: LogCryp,
|
|
pub target: LogCryp,
|
|
pub event: Event,
|
|
}
|
|
|
|
impl Resolution {
|
|
fn new(source: &Cryp, target: &Cryp) -> Resolution {
|
|
Resolution {
|
|
source: LogCryp { id: source.id, name: source.name.clone() },
|
|
target: LogCryp { id: target.id, name: target.name.clone() },
|
|
event: Event::Incomplete,
|
|
}
|
|
}
|
|
|
|
fn event(mut self, e: Event) -> Resolution {
|
|
self.event = e;
|
|
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: Category },
|
|
Healing { skill: Skill, amount: u64, overhealing: u64 },
|
|
Recharge { skill: Skill, red: u64, blue: u64 },
|
|
Inversion { skill: Skill },
|
|
Reflection { skill: Skill },
|
|
Effect { skill: Skill, effect: Effect, duration: u8 },
|
|
AoeSkill { skill: Skill },
|
|
Skill { skill: Skill },
|
|
Removal { effect: Effect },
|
|
TargetKo { skill: Skill },
|
|
// skill not necessary but makes it neater as all events are arrays in js
|
|
Ko { skill: Skill },
|
|
Incomplete,
|
|
// not used
|
|
Evasion { skill: Skill, evasion_rating: u64 },
|
|
}
|
|
|
|
type Resolutions = Vec<Resolution>;
|
|
pub type Cooldown = Option<u8>;
|
|
|
|
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
pub enum Effect {
|
|
// physical
|
|
Stun,
|
|
Parry,
|
|
Block,
|
|
Bleed,
|
|
Leech,
|
|
Airborne,
|
|
Untouchable,
|
|
Deadly,
|
|
Vulnerable,
|
|
Fury,
|
|
Blind,
|
|
Snare,
|
|
Clutch,
|
|
Injured,
|
|
|
|
Reflect,
|
|
|
|
Taunt,
|
|
Impurity,
|
|
Invert,
|
|
Strangle,
|
|
Strangling,
|
|
|
|
// magic
|
|
Hex,
|
|
Ruin,
|
|
Curse,
|
|
Banish,
|
|
Slow,
|
|
Haste,
|
|
Enslave,
|
|
Mesmerise,
|
|
Amplify,
|
|
Silence,
|
|
Wither, // Reduce green dmg (healing) taken
|
|
|
|
// corrupt is the buff that applies
|
|
// corruption the dmg debuff
|
|
Corrupt,
|
|
Corruption,
|
|
|
|
// hostility is the buff
|
|
// hatred is the increased damage
|
|
Hostility,
|
|
Hatred,
|
|
|
|
// magic immunity
|
|
Scatter,
|
|
|
|
// effects over time
|
|
Triage,
|
|
Decay,
|
|
Regen,
|
|
Siphon,
|
|
|
|
SpeedSiphon,
|
|
SpeedIncrease,
|
|
|
|
Ko,
|
|
}
|
|
|
|
impl Effect {
|
|
pub fn immune(&self, skill: Skill) -> bool {
|
|
match self {
|
|
Effect::Parry => match skill.category() {
|
|
Category::Blue => false,
|
|
Category::Red => true,
|
|
_ => false,
|
|
},
|
|
Effect::Banish => true,
|
|
Effect::Injured => match skill.category() {
|
|
Category::Green => true,
|
|
Category::GreenTick => true,
|
|
_ => false,
|
|
},
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn disables_skill(&self, skill: Skill) -> bool {
|
|
match self {
|
|
Effect::Stun => true,
|
|
Effect::Hex => true,
|
|
Effect::Ruin => true,
|
|
Effect::Banish => true,
|
|
Effect::Strangle => true,
|
|
Effect::Strangling => skill != Skill::StrangleTick,
|
|
Effect::Silence => match skill.category() {
|
|
Category::Blue => true,
|
|
Category::Red => false,
|
|
_ => false,
|
|
},
|
|
Effect::Snare => match skill.category() {
|
|
Category::Blue => false,
|
|
Category::Red => true,
|
|
_ => false,
|
|
},
|
|
|
|
Effect::Ko => match skill.category() {
|
|
Category::BlueTick => false,
|
|
Category::GreenTick => false,
|
|
_ => true,
|
|
},
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn modifications(&self) -> Vec<Stat> {
|
|
match self {
|
|
Effect::Vulnerable => vec![Stat::RedDamageTaken],
|
|
Effect::Block => vec![Stat::RedDamageTaken],
|
|
|
|
Effect::Hatred => vec![Stat::RedDamage, Stat::BlueDamage],
|
|
|
|
Effect::Amplify => vec![Stat::RedDamage, Stat::BlueDamage],
|
|
Effect::Curse => vec![Stat::BlueDamageTaken],
|
|
|
|
Effect::Impurity => vec![Stat::GreenDamage],
|
|
Effect::Wither => vec![Stat::GreenDamageTaken],
|
|
|
|
Effect::Haste => vec![Stat::Speed],
|
|
Effect::Slow => vec![Stat::Speed],
|
|
|
|
Effect::Scatter => vec![Stat::BlueDamageTaken, Stat::GreenDamageTaken, Stat::RedDamageTaken],
|
|
|
|
_ => vec![],
|
|
}
|
|
}
|
|
|
|
pub fn apply(&self, value: u64, meta: Option<EffectMeta>) -> u64 {
|
|
match self {
|
|
Effect::Vulnerable => value.pct(150),
|
|
Effect::Block => value.pct(50),
|
|
|
|
Effect::Amplify => value.pct(150),
|
|
Effect::Curse => value.pct(150),
|
|
|
|
Effect::Haste => value.pct(150),
|
|
Effect::Slow => value.pct(50),
|
|
|
|
Effect::Impurity => value.pct(150),
|
|
Effect::Wither => value.pct(50),
|
|
|
|
Effect::Scatter => value >> 1,
|
|
|
|
Effect::Hatred => value + match meta {
|
|
Some(EffectMeta::AddedDamage(d)) => d,
|
|
_ => panic!("hatred meta not damage"),
|
|
},
|
|
|
|
_ => {
|
|
info!("{:?} does not have a mod effect", self);
|
|
return value;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn category(&self) -> Category {
|
|
match self {
|
|
// physical
|
|
Effect::Stun => Category::Debuff,
|
|
Effect::Block => Category::Buff,
|
|
Effect::Parry => Category::Buff,
|
|
Effect::Bleed => Category::Debuff,
|
|
Effect::Leech => Category::Debuff,
|
|
Effect::Airborne => Category::Debuff,
|
|
Effect::Untouchable => Category::Buff,
|
|
Effect::Deadly => Category::Buff,
|
|
Effect::Vulnerable => Category::Debuff,
|
|
Effect::Fury => Category::Buff,
|
|
Effect::Blind => Category::Debuff,
|
|
Effect::Snare => Category::Debuff,
|
|
Effect::Clutch => Category::Buff,
|
|
Effect::Taunt => Category::Buff,
|
|
Effect::Injured => Category::Debuff,
|
|
Effect::Strangle => Category::Debuff,
|
|
Effect::Strangling => Category::Buff,
|
|
|
|
// magic
|
|
Effect::Hex => Category::Debuff,
|
|
Effect::Ruin => Category::Debuff,
|
|
Effect::Curse => Category::Debuff,
|
|
Effect::Banish => Category::Debuff, // todo randomise
|
|
// Effect::Banish => rng.gen_bool(0.5),
|
|
|
|
Effect::Slow => Category::Debuff,
|
|
Effect::Haste => Category::Buff,
|
|
Effect::Hatred => Category::Buff,
|
|
Effect::Reflect => Category::Buff,
|
|
Effect::Enslave => Category::Debuff,
|
|
Effect::Mesmerise => Category::Debuff,
|
|
Effect::Amplify => Category::Buff,
|
|
Effect::Silence => Category::Debuff,
|
|
Effect::Wither => Category::Debuff,
|
|
|
|
Effect::Corrupt => Category::Buff,
|
|
Effect::Corruption => Category::Debuff,
|
|
|
|
Effect::Hostility => Category::Buff,
|
|
|
|
// magic
|
|
Effect::Impurity => Category::Buff,
|
|
Effect::Scatter => Category::Buff,
|
|
Effect::Invert => Category::Buff,
|
|
|
|
// effects over time
|
|
Effect::Triage => Category::Buff,
|
|
Effect::Decay => Category::Debuff,
|
|
Effect::Regen => Category::Buff,
|
|
Effect::Siphon => Category::Debuff,
|
|
|
|
Effect::SpeedSiphon => Category::Debuff,
|
|
Effect::SpeedIncrease => Category::Buff,
|
|
|
|
Effect::Ko => Category::Ko,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
pub enum Category {
|
|
Red,
|
|
Blue,
|
|
Green,
|
|
|
|
RedDamage,
|
|
BlueDamage,
|
|
GreenDamage,
|
|
|
|
RedTick,
|
|
BlueTick,
|
|
GreenTick,
|
|
|
|
Buff,
|
|
Debuff,
|
|
|
|
Ko,
|
|
}
|
|
|
|
#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)]
|
|
pub enum Skill {
|
|
Attack,
|
|
Debuff,
|
|
Block, // reduce damage
|
|
Stun,
|
|
|
|
// -----------------
|
|
// Nature
|
|
// -----------------
|
|
Parry, // avoid all damage
|
|
Riposte,
|
|
Snare,
|
|
Injure,
|
|
|
|
Recharge,
|
|
Reflect,
|
|
Ruin,
|
|
Slay,
|
|
Sleep,
|
|
Clutch,
|
|
Taunt,
|
|
Impurity,
|
|
ImpureBlast,
|
|
Invert,
|
|
|
|
Strangle,
|
|
StrangleTick,
|
|
|
|
Strike,
|
|
StrikeII,
|
|
StrikeIII,
|
|
// Evade, // actively evade
|
|
|
|
// -----------------
|
|
// Nonviolence
|
|
// -----------------
|
|
Heal,
|
|
Triage, // hot
|
|
TriageTick,
|
|
Throw, // no damage stun, adds vulnerable
|
|
|
|
// Sleep,
|
|
// Nightmare,
|
|
|
|
// -------------------
|
|
// Destruction
|
|
// -------------------
|
|
Blast,
|
|
Amplify,
|
|
Decay, // dot
|
|
DecayTick, // dot
|
|
Siphon,
|
|
SiphonTick,
|
|
Curse,
|
|
|
|
Hostility,
|
|
|
|
Corrupt,
|
|
CorruptionTick,
|
|
|
|
|
|
// -----------------
|
|
// Purity
|
|
// -----------------
|
|
Scatter,
|
|
Silence,
|
|
Purify,
|
|
Purge,
|
|
|
|
// -----------------
|
|
// Chaos
|
|
// -----------------
|
|
Banish,
|
|
Chaos,
|
|
Hex,
|
|
Haste,
|
|
HasteStrike,
|
|
|
|
// used by tests, no cd, 100% multiplier
|
|
TestAttack,
|
|
TestHeal,
|
|
TestTouch, // No damage
|
|
TestStun,
|
|
TestBlock,
|
|
TestParry,
|
|
TestSiphon,
|
|
}
|
|
|
|
impl Skill {
|
|
pub fn multiplier(&self) -> u64 {
|
|
match self {
|
|
// Attack Base
|
|
Skill::Attack => 80, // Base
|
|
|
|
Skill::Blast => 110, // BB
|
|
Skill::Chaos => 40, // BR
|
|
Skill::Heal => 130, //GG
|
|
Skill::SiphonTick => 40, // GB
|
|
Skill::Slay => 70, // RG
|
|
Skill::Strike => 90, //RR
|
|
Skill::StrikeII => 110,
|
|
Skill::StrikeIII => 130,
|
|
|
|
// Block Base
|
|
Skill::CorruptionTick => 80,
|
|
Skill::Purify => 45, //Green dmg (heal)
|
|
Skill::Recharge => 85, //restore red and blue life (heal)
|
|
Skill::Reflect => 45, //restore blue life (heal)
|
|
|
|
Skill::Parry => 110,
|
|
Skill::Riposte => 70,
|
|
|
|
// Stun Base
|
|
Skill::Sleep => 240, //Green dmg (heal)
|
|
Skill::StrangleTick => 65,
|
|
|
|
// Debuff Base
|
|
Skill::Silence => 55, // Deals more per blue skill on target
|
|
Skill::Snare => 40, // Deals more per red skill on target
|
|
Skill::DecayTick => 25,
|
|
|
|
// Buff base
|
|
Skill::ImpureBlast => 25,
|
|
Skill::HasteStrike => 30,
|
|
Skill::Taunt => 80,
|
|
Skill::TriageTick => 75,
|
|
Skill::Scatter => 140,
|
|
|
|
_ => 100,
|
|
}
|
|
}
|
|
|
|
pub fn duration(&self) -> u8 {
|
|
match self {
|
|
Skill::Block => 1,
|
|
Skill::Parry => 2,
|
|
Skill::Clutch => 1,
|
|
Skill::Debuff => 3,
|
|
Skill::Reflect => 1,
|
|
|
|
Skill::Injure => 2,
|
|
|
|
Skill::Strangle => 2,
|
|
|
|
Skill::Stun => 2,
|
|
Skill::Sleep => 3,
|
|
|
|
Skill::Throw => 1,
|
|
Skill::Snare => 3,
|
|
|
|
Skill::Taunt => 2,
|
|
Skill::Impurity => 3,
|
|
Skill::Invert => 1,
|
|
|
|
Skill::Hex => 2,
|
|
Skill::Ruin => 1,
|
|
Skill::Curse => 2,
|
|
Skill::Banish => 1,
|
|
|
|
Skill::Haste => 2,
|
|
|
|
Skill::Amplify => 2,
|
|
Skill::Silence => 3,
|
|
|
|
Skill::Hostility => 2, // Primary Buff
|
|
Skill::Corrupt => 2, // Primary Buff
|
|
|
|
Skill::Scatter => 2,
|
|
|
|
Skill::Triage => 3,
|
|
Skill::Decay => 3,
|
|
Skill::Siphon => 2,
|
|
|
|
_ => {
|
|
info!("{:?} does not have a duration", self);
|
|
return 1;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn secondary_duration(&self) -> u8 {
|
|
match self {
|
|
Skill::Hostility => 5, // Increased dmg buff
|
|
Skill::Corrupt => 3, // Damage over time
|
|
Skill::Throw => 3, // Inc dmg taken debuff
|
|
|
|
_ => {
|
|
info!("{:?} does not have a secondary duration", self);
|
|
return 1;
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
|
|
pub fn base_cd(&self) -> Cooldown {
|
|
match self {
|
|
Skill::Attack => None,
|
|
Skill::Debuff => Some(1),
|
|
Skill::Strike => None,
|
|
Skill::StrikeII => None,
|
|
Skill::StrikeIII => None,
|
|
Skill::Block => None, // reduce damage
|
|
Skill::Parry => Some(2), // avoid all damage
|
|
Skill::Riposte => None, // used on parry
|
|
Skill::Snare => Some(2),
|
|
Skill::Stun => Some(2),
|
|
Skill::Heal => None,
|
|
Skill::Triage => None, // hot
|
|
Skill::TriageTick => None,
|
|
Skill::Throw => Some(1), // no damage stun, adds vulnerable
|
|
Skill::Blast => None,
|
|
Skill::Chaos => None,
|
|
Skill::Amplify => Some(1),
|
|
Skill::Impurity => Some(3),
|
|
Skill::ImpureBlast => None,
|
|
Skill::Invert => Some(2),
|
|
Skill::Decay => Some(1), // dot
|
|
Skill::DecayTick => None,
|
|
Skill::Siphon => None,
|
|
Skill::SiphonTick => None,
|
|
Skill::Curse => Some(1),
|
|
Skill::Scatter => Some(2),
|
|
Skill::Silence => Some(2),
|
|
Skill::Purify => None,
|
|
Skill::Purge => None,
|
|
Skill::Banish => Some(1),
|
|
Skill::Hex => Some(1),
|
|
Skill::Haste => Some(2),
|
|
Skill::HasteStrike => None, // Used in haste
|
|
|
|
Skill::Reflect => Some(2),
|
|
Skill::Recharge => Some(2),
|
|
Skill::Ruin => Some(3),
|
|
Skill::Slay => None,
|
|
Skill::Sleep => Some(3),
|
|
|
|
Skill::Strangle => Some(2),
|
|
Skill::StrangleTick => None,
|
|
Skill::Clutch => Some(2),
|
|
Skill::Taunt => Some(2),
|
|
Skill::Injure => Some(2),
|
|
|
|
Skill::Corrupt => Some(1),
|
|
Skill::CorruptionTick => None,
|
|
|
|
Skill::Hostility => Some(1),
|
|
|
|
// -----------------
|
|
// Test
|
|
// -----------------
|
|
Skill::TestAttack => None,
|
|
Skill::TestHeal => None,
|
|
Skill::TestTouch => None,
|
|
Skill::TestStun => None,
|
|
Skill::TestBlock => None,
|
|
Skill::TestSiphon => None,
|
|
Skill::TestParry => None,
|
|
}
|
|
}
|
|
|
|
pub fn category(&self) -> Category {
|
|
match self {
|
|
Skill::Attack => Category::Red,
|
|
Skill::Strike => Category::Red,
|
|
Skill::StrikeII => Category::Red,
|
|
Skill::StrikeIII => Category::Red,
|
|
Skill::Injure => Category::Red,
|
|
Skill::Strangle => Category::Red,
|
|
Skill::StrangleTick => Category::Red,
|
|
Skill::Block => Category::Red, // reduce damage
|
|
Skill::Parry => Category::Red, // avoid all damage
|
|
Skill::Riposte => Category::Red, // avoid all damage
|
|
Skill::Snare => Category::Red,
|
|
Skill::Clutch => Category::Red,
|
|
Skill::Stun => Category::Red,
|
|
Skill::Slay => Category::Red,
|
|
Skill::Taunt => Category::Red,
|
|
Skill::HasteStrike => Category::Red,
|
|
|
|
Skill::Heal => Category::Green,
|
|
Skill::Triage => Category::Green, // hot
|
|
Skill::TriageTick => Category::GreenTick, // hot
|
|
Skill::Throw => Category::Green,
|
|
Skill::Purify => Category::Green,
|
|
Skill::Recharge => Category::Green,
|
|
Skill::Reflect => Category::Green,
|
|
Skill::Haste => Category::Green,
|
|
Skill::Impurity => Category::Green,
|
|
Skill::Invert => Category::Green,
|
|
Skill::Sleep => Category::Green,
|
|
|
|
Skill::ImpureBlast => Category::Blue,
|
|
Skill::Scatter => Category::Blue,
|
|
Skill::Blast => Category::Blue,
|
|
Skill::Chaos => Category::Blue,
|
|
Skill::Amplify => Category::Blue,
|
|
Skill::Decay => Category::Blue, // dot
|
|
Skill::DecayTick => Category::BlueTick, // hot
|
|
Skill::Siphon => Category::Blue,
|
|
Skill::SiphonTick => Category::BlueTick, // hot
|
|
Skill::Curse => Category::Blue,
|
|
Skill::Silence => Category::Blue,
|
|
Skill::Purge => Category::Blue,
|
|
Skill::Banish => Category::Blue,
|
|
Skill::Hex => Category::Blue,
|
|
Skill::Debuff => Category::Blue,
|
|
Skill::Ruin => Category::Blue,
|
|
Skill::Hostility => Category::Blue,
|
|
Skill::Corrupt => Category::Blue,
|
|
Skill::CorruptionTick => Category::Blue,
|
|
|
|
// -----------------
|
|
// Test
|
|
// -----------------
|
|
|
|
Skill::TestAttack => Category::Red,
|
|
Skill::TestHeal => Category::Green,
|
|
Skill::TestTouch => Category::Red,
|
|
Skill::TestStun => Category::Red,
|
|
Skill::TestParry => Category::Red,
|
|
Skill::TestBlock => Category::Red,
|
|
Skill::TestSiphon => Category::Blue,
|
|
}
|
|
}
|
|
|
|
pub fn ko_castable(&self) -> bool {
|
|
match self {
|
|
Skill::TriageTick => true,
|
|
Skill::DecayTick => true,
|
|
Skill::SiphonTick => true,
|
|
Skill::CorruptionTick => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn speed(&self) -> u8 {
|
|
match self {
|
|
// -----------------
|
|
// Test
|
|
// -----------------
|
|
Skill::TestTouch => 10,
|
|
Skill::TestStun => 5,
|
|
Skill::TestBlock => 10,
|
|
Skill::TestParry => 10,
|
|
Skill::TestSiphon => 10,
|
|
|
|
Skill::Strike => u8::max_value(),
|
|
Skill::StrikeII => Skill::Strike.speed(),
|
|
Skill::StrikeIII => Skill::Strike.speed(),
|
|
|
|
Skill::SiphonTick => Item::from(Skill::Siphon).speed(),
|
|
Skill::DecayTick => Item::from(Skill::Decay).speed(),
|
|
Skill::TriageTick => Item::from(Skill::Triage).speed(),
|
|
Skill::StrangleTick => Item::from(Skill::Strangle).speed(),
|
|
Skill::CorruptionTick => Item::from(Skill::Corrupt).speed(),
|
|
|
|
_ => Item::from(*self).speed(),
|
|
}
|
|
}
|
|
|
|
pub fn aoe(&self) -> bool {
|
|
match self {
|
|
Skill::Ruin => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn self_targeting(&self) -> bool {
|
|
match self {
|
|
Skill::Block => true,
|
|
Skill::Parry => true,
|
|
Skill::Clutch => true,
|
|
Skill::Corrupt => true,
|
|
|
|
Skill::TestBlock => true,
|
|
Skill::TestParry => true,
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
pub fn defensive(&self) -> bool {
|
|
let mut rng = thread_rng();
|
|
|
|
match self {
|
|
Skill::Heal |
|
|
Skill::Triage |
|
|
Skill::Purify |
|
|
Skill::Parry |
|
|
Skill::Clutch |
|
|
Skill::Scatter |
|
|
Skill::Recharge |
|
|
Skill::Reflect |
|
|
Skill::Haste |
|
|
Skill::Invert |
|
|
Skill::Amplify |
|
|
Skill::Hostility |
|
|
Skill::Corrupt |
|
|
Skill::Block => true,
|
|
|
|
Skill::Banish => rng.gen_bool(0.5),
|
|
|
|
_ => false,
|
|
}
|
|
}
|
|
}
|
|
|
|
fn touch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
target.deal_red_damage(skill, 0)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
|
|
return results;
|
|
}
|
|
|
|
fn attack(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.red_damage().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 Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.red_damage().pct(skill.multiplier());
|
|
target.deal_red_damage(skill, amount)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
|
|
return results;
|
|
}
|
|
|
|
fn injure(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.red_damage().pct(skill.multiplier());
|
|
target.deal_red_damage(skill, amount)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
|
|
let effect = CrypEffect::new(Effect::Injured, 2);
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;
|
|
}
|
|
|
|
fn stun(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Stun, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;
|
|
}
|
|
|
|
fn sleep(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Stun, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
let amount = source.green_damage().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 clutch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Clutch, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;
|
|
}
|
|
|
|
fn taunt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let red_amount = source.red_damage().pct(skill.multiplier());
|
|
results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, 0)));
|
|
|
|
let effect = CrypEffect::new(Effect::Taunt, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;
|
|
}
|
|
|
|
fn throw(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let stun = CrypEffect::new(Effect::Stun, skill.duration());
|
|
let vulnerable = CrypEffect::new(Effect::Vulnerable, skill.secondary_duration());
|
|
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, stun)));
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, vulnerable)));
|
|
|
|
return results;
|
|
}
|
|
|
|
fn strangle(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let target_stun = CrypEffect::new(Effect::Strangle, skill.duration())
|
|
.set_tick(Cast::new_tick(source, target, Skill::StrangleTick));
|
|
|
|
let attacker_immunity = CrypEffect::new(Effect::Strangling, skill.duration());
|
|
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, target_stun)));
|
|
results.push(Resolution::new(source, source).event(source.add_effect(skill, attacker_immunity)));
|
|
return strangle_tick(source, target, results, Skill::StrangleTick);
|
|
}
|
|
|
|
fn strangle_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.red_damage().pct(skill.multiplier());
|
|
target.deal_red_damage(skill, amount)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
|
|
// remove immunity if target ko
|
|
if target.is_ko() {
|
|
let i = source.effects
|
|
.iter()
|
|
.position(|e| e.effect == Effect::Strangling)
|
|
.expect("no strangling on cryp");
|
|
source.effects.remove(i);
|
|
results.push(Resolution::new(source, source).event(Event::Removal { effect: Effect::Strangling }));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
fn block(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let block = CrypEffect::new(Effect::Block, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, block)));
|
|
return results;
|
|
}
|
|
|
|
fn parry(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let red_amount = source.red_damage().pct(skill.multiplier());
|
|
results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, 0)));
|
|
|
|
let effect = CrypEffect::new(Effect::Parry, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;
|
|
}
|
|
|
|
fn riposte(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.red_damage().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 snare(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let snare = CrypEffect::new(Effect::Snare, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, snare)));
|
|
|
|
let s_multi = target.skills
|
|
.iter()
|
|
.fold(100, |acc, cs| match cs.skill.category() {
|
|
Category::Red => acc + 35,
|
|
_ => acc,
|
|
});
|
|
|
|
let amount = source.red_damage().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)));
|
|
|
|
|
|
return results;
|
|
}
|
|
|
|
fn slay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.red_damage().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));
|
|
};
|
|
},
|
|
_ => results.push(Resolution::new(source, target).event(e)),
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
fn heal(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.green_damage().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 Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Triage, skill.duration())
|
|
.set_tick(Cast::new_tick(source, target, Skill::TriageTick));
|
|
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return triage_tick(source, target, results, Skill::TriageTick);
|
|
}
|
|
|
|
fn triage_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.green_damage().pct(skill.multiplier());
|
|
target.deal_green_damage(Skill::TriageTick, amount)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
return results;
|
|
}
|
|
|
|
fn chaos(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let mut rng = thread_rng();
|
|
let b_rng: u64 = rng.gen_range(0, 30);
|
|
let amount = source.blue_damage().pct(skill.multiplier() + 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(0, 30);
|
|
let amount = source.red_damage().pct(skill.multiplier() + r_rng);
|
|
target.deal_red_damage(skill, amount)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
return results;
|
|
}
|
|
|
|
fn blast(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.blue_damage().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 Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amplify = CrypEffect::new(Effect::Amplify, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, amplify)));
|
|
return results;;
|
|
}
|
|
|
|
fn haste(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Haste, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
fn debuff(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Slow, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
fn decay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let wither = CrypEffect::new(Effect::Wither, skill.duration());
|
|
let decay = CrypEffect::new(Effect::Decay, skill.duration())
|
|
.set_tick(Cast::new_tick(source, target, Skill::DecayTick));
|
|
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, decay)));
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, wither)));
|
|
return decay_tick(source, target, results, Skill::DecayTick);
|
|
}
|
|
|
|
fn decay_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.blue_damage().pct(skill.multiplier());
|
|
target.deal_blue_damage(skill, amount)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
return results;
|
|
}
|
|
|
|
// corrupt is the buff effect
|
|
// when attacked it runs corruption and applies a debuff
|
|
fn corrupt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Corrupt, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
fn corruption(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Corruption, skill.secondary_duration())
|
|
.set_tick(Cast::new_tick(source, target, Skill::CorruptionTick));
|
|
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return corruption_tick(source, target, results, Skill::CorruptionTick);
|
|
}
|
|
|
|
fn corruption_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.blue_damage().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 ruin(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Ruin, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
|
|
fn hex(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let hex = CrypEffect::new(Effect::Hex, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, hex)));
|
|
return results;;
|
|
}
|
|
|
|
fn hostility(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Hostility, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
fn hatred(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Hatred, skill.secondary_duration())
|
|
.set_meta(EffectMeta::AddedDamage(amount));
|
|
|
|
results.push(Resolution::new(source, target).event(target.add_effect(reflect_skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
fn curse(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let curse = CrypEffect::new(Effect::Curse, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(Skill::Curse, curse)));
|
|
return results;;
|
|
}
|
|
|
|
fn impurity(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Impurity, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
fn invert(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Invert, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
return results;;
|
|
}
|
|
|
|
fn reflect(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Reflect, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)));
|
|
|
|
let blue_amount = source.blue_damage().pct(skill.multiplier());
|
|
results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount)));
|
|
|
|
return results;;
|
|
}
|
|
|
|
fn recharge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let red_amount = source.red_damage().pct(skill.multiplier());
|
|
let blue_amount = source.blue_damage().pct(skill.multiplier());
|
|
|
|
results.push(Resolution::new(source, target).event(target.recharge(skill, red_amount, blue_amount)));
|
|
return results;
|
|
}
|
|
|
|
fn siphon(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let siphon = CrypEffect::new(Effect::Siphon, skill.duration())
|
|
.set_tick(Cast::new_tick(source, target, Skill::SiphonTick));
|
|
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, siphon)));
|
|
return siphon_tick(source, target, results, Skill::SiphonTick);
|
|
}
|
|
|
|
fn siphon_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let amount = source.blue_damage().pct(skill.multiplier());
|
|
let siphon_events = target.deal_blue_damage(Skill::SiphonTick, amount);
|
|
|
|
for e in siphon_events {
|
|
match e {
|
|
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => {
|
|
results.push(Resolution::new(source, target).event(e));
|
|
let heal = source.deal_green_damage(Skill::SiphonTick, amount);
|
|
for h in heal {
|
|
results.push(Resolution::new(source, source).event(h));
|
|
};
|
|
},
|
|
_ => results.push(Resolution::new(source, target).event(e)),
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
fn scatter(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let effect = CrypEffect::new(Effect::Scatter, skill.duration())
|
|
.set_meta(EffectMeta::ScatterTarget(target.id));
|
|
|
|
let blue_amount = source.blue_damage().pct(skill.multiplier());
|
|
results.push(Resolution::new(source, target).event(target.recharge(skill, 0, blue_amount)));
|
|
|
|
results.push(Resolution::new(source, target).event(source.add_effect(skill, effect)));
|
|
return results;
|
|
}
|
|
|
|
fn scatter_hit(source: &Cryp, target: &Cryp, mut results: Resolutions, game: &mut Game, event: Event) -> Resolutions {
|
|
match event {
|
|
Event::Damage { amount, skill, mitigation: _, colour } => {
|
|
let scatter = target.effects.iter().find(|e| e.effect == Effect::Scatter).unwrap();
|
|
|
|
if let Some(EffectMeta::ScatterTarget(scatter_target_id)) = scatter.meta {
|
|
let mut scatter_target = game.cryp_by_id(scatter_target_id).unwrap();
|
|
|
|
let res = match colour {
|
|
Category::RedDamage => scatter_target.deal_red_damage(skill, amount),
|
|
Category::BlueDamage => scatter_target.deal_blue_damage(skill, amount),
|
|
Category::GreenDamage => scatter_target.deal_green_damage(skill, amount),
|
|
_ => panic!("{:?} unknown damage type", colour),
|
|
};
|
|
|
|
results.push(Resolution::new(target, scatter_target).event(Event::Skill { skill: Skill::Scatter }));
|
|
res.into_iter().for_each(|e| results.push(Resolution::new(&source, &scatter_target).event(e)));
|
|
} else {
|
|
panic!("not a scatter target {:?}", scatter);
|
|
}
|
|
|
|
return results;
|
|
},
|
|
_ => panic!("{:?} scatter hit not damage event", event),
|
|
}
|
|
}
|
|
|
|
fn silence(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let silence = CrypEffect::new(Effect::Silence, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, silence)));
|
|
|
|
let s_multi = target.skills
|
|
.iter()
|
|
.fold(100, |acc, cs| match cs.skill.category() {
|
|
Category::Blue => acc + 45,
|
|
_ => acc,
|
|
});
|
|
|
|
let amount = source.blue_damage().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)));
|
|
|
|
return results;
|
|
}
|
|
|
|
fn purge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
while let Some(i) = target.effects
|
|
.iter()
|
|
.position(|ce| [Category::Buff, Category::Buff].contains(&ce.effect.category())) {
|
|
let ce = target.effects.remove(i);
|
|
results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect }));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
fn purify(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
results.push(Resolution::new(source, target).event(Event::Skill { skill }));
|
|
let amount = source.green_damage().pct(skill.multiplier());
|
|
while let Some(i) = target.effects
|
|
.iter()
|
|
.position(|ce| Category::Debuff == ce.effect.category()) {
|
|
let ce = target.effects.remove(i);
|
|
results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect }));
|
|
target.deal_green_damage(skill, amount)
|
|
.into_iter()
|
|
.for_each(|e| results.push(Resolution::new(source, target).event(e)));
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
fn banish(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions, skill: Skill) -> Resolutions {
|
|
let banish = CrypEffect::new(Effect::Banish, skill.duration());
|
|
results.push(Resolution::new(source, target).event(target.add_effect(skill, banish)));
|
|
return results;
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use skill::*;
|
|
|
|
#[test]
|
|
fn heal_test() {
|
|
let mut x = Cryp::new()
|
|
.named(&"muji".to_string())
|
|
.learn(Skill::Heal);
|
|
|
|
let mut y = Cryp::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 = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::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 = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::new()
|
|
.named(&"camel".to_string());
|
|
|
|
// ensure it doesn't have 0 pd
|
|
x.red_damage.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::TestAttack);
|
|
|
|
let Resolution { source: _, target: _, event } = results.remove(0);
|
|
match event {
|
|
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 50),
|
|
_ => panic!("not damage"),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn clutch_test() {
|
|
let mut x = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::new()
|
|
.named(&"camel".to_string());
|
|
|
|
x.red_damage.force(10000000000000); // multiplication of int max will cause overflow
|
|
|
|
clutch(&mut y.clone(), &mut y, vec![], Skill::Clutch);
|
|
assert!(y.affected(Effect::Clutch));
|
|
|
|
let mut results = attack(&mut x, &mut y, vec![], Skill::Attack);
|
|
assert!(y.green_life() == 1);
|
|
|
|
let Resolution { source: _, target: _, event } = results.remove(0);
|
|
match event {
|
|
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023),
|
|
_ => panic!("not damage"),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn injure_test() {
|
|
let mut x = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::new()
|
|
.named(&"camel".to_string());
|
|
|
|
resolve(Skill::Injure, &mut x, &mut y, vec![]);
|
|
assert!(y.immune(Skill::Heal).is_some());
|
|
// resolutions = heal(&mut y.clone(), &mut y, resolutions);
|
|
}
|
|
|
|
#[test]
|
|
fn invert_test() {
|
|
let mut x = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::new()
|
|
.named(&"camel".to_string());
|
|
|
|
// give red shield but reduce to 0
|
|
y.red_life.force(64);
|
|
y.red_life.reduce(64);
|
|
x.red_damage.force(256 + 64);
|
|
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::TestHeal);
|
|
assert!(y.green_life() == 768);
|
|
|
|
// attack should heal and recharge red shield
|
|
let mut results = attack(&mut x, &mut y, vec![], Skill::TestAttack);
|
|
assert!(y.green_life() == 1024);
|
|
|
|
match results.remove(0).event {
|
|
Event::Inversion { skill } => assert_eq!(skill, Skill::TestAttack),
|
|
_ => panic!("not inversion"),
|
|
};
|
|
|
|
match results.remove(0).event {
|
|
Event::Healing { skill: _, overhealing: _, amount } => assert_eq!(amount, 256),
|
|
_ => panic!("not healing from inversion"),
|
|
};
|
|
|
|
match results.remove(0).event {
|
|
Event::Recharge { skill: _, red, blue: _ } => assert_eq!(red, 64),
|
|
_ => panic!("not recharge from inversion"),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn reflect_test() {
|
|
let mut x = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::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::TestAttack, &mut x, &mut y, results);
|
|
|
|
assert!(x.green_life() == 768);
|
|
|
|
let Resolution { source: _, target: _, event } = results.remove(0);
|
|
match event {
|
|
Event::Reflection { skill } => assert_eq!(skill, Skill::TestAttack),
|
|
_ => panic!("not reflection"),
|
|
};
|
|
|
|
let Resolution { source: _, target: _, event } = results.remove(0);
|
|
match event {
|
|
Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 256),
|
|
_ => panic!("not damage"),
|
|
};
|
|
}
|
|
|
|
#[test]
|
|
fn siphon_test() {
|
|
let mut x = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::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 } = results.remove(0);
|
|
match event {
|
|
Event::Effect { effect, skill: _, duration: _ } => assert_eq!(effect, Effect::Siphon),
|
|
_ => panic!("not siphon"),
|
|
};
|
|
|
|
let Resolution { source: _, target: _, event } = 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 } = 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 = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::new()
|
|
.named(&"pretaliation".to_string());
|
|
|
|
// ensure it doesn't have 0 sd
|
|
x.blue_damage.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 = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
let mut y = Cryp::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 } = 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 = Cryp::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 = Cryp::new()
|
|
.named(&"muji".to_string());
|
|
|
|
x.blue_damage.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_damage(), 75);
|
|
}
|
|
|
|
#[test]
|
|
fn purify_test() {
|
|
let mut x = Cryp::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));
|
|
}
|
|
}
|