use rand::{thread_rng, Rng}; use uuid::Uuid; use cryp::{Cryp, CrypEffect, Stat}; use vbox::{Var}; use game::{Game}; #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub struct Cast { pub id: Uuid, pub source_team_id: Uuid, pub source_cryp_id: Uuid, pub target_cryp_id: Uuid, pub skill: Skill, pub speed: u64, pub resolutions: Resolutions, } impl Cast { pub fn new(source_cryp_id: Uuid, source_team_id: Uuid, target_cryp_id: Uuid, skill: Skill) -> Cast { return Cast { id: Uuid::new_v4(), source_cryp_id, source_team_id, target_cryp_id, skill, speed: 0, resolutions: vec![], }; } pub fn new_tick(source: &mut Cryp, target: &mut Cryp, skill: Skill) -> Cast { Cast::new(source.id, source.account, target.id, skill) } pub fn used_cooldown(&self) -> bool { return self.skill.base_cd().is_some(); } } pub type Disable = Vec; pub type Immunity = Vec; #[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 { TargetKo, Disable { disable: Disable }, Immunity { immunity: Immunity }, Damage { amount: u64, mitigation: u64, category: Category }, Healing { amount: u64, overhealing: u64 }, Recharge { red: u64, blue: u64 }, Inversion { healing: u64, damage: u64, recharge: u64, category: Category }, Effect { effect: Effect, duration: u8 }, Removal { effect: Effect }, Evasion { skill: Skill, evasion_rating: u64 }, Incomplete, } type Resolutions = Vec; pub type Cooldown = Option; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Effect { // physical Stun, Parry, Block, Bleed, Leech, Airborne, Untouchable, Deadly, Vulnerable, Fury, Blind, Snare, Clutch, Reflect, Empower, Taunt, Invert, Strangle, Strangling, // magic Hex, Ruin, Curse, Banish, Slow, Haste, Enslave, Mesmerise, Amplify, Silence, // magic immunity Shield, // 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::Shield => match skill.category() { Category::Blue => true, Category::Red => false, _ => false, }, Effect::Strangle => skill != Skill::StrangleTick, Effect::Strangling => true, Effect::Banish => true, _ => 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, _ => true, }, _ => false, } } pub fn modifications(&self) -> Vec { match self { Effect::Empower => vec![Stat::RedDamage], Effect::Vulnerable => vec![Stat::RedDamageTaken], Effect::Block => vec![Stat::RedDamageTaken], Effect::Amplify => vec![Stat::BlueDamage], Effect::Curse => vec![Stat::BlueDamageTaken], Effect::Haste => vec![Stat::Speed], Effect::Slow => vec![Stat::Speed], _ => vec![], } } // maybe increase by rng // roll little endian bits // and OR with base stat pub fn apply(&self, value: u64) -> u64 { match self { Effect::Empower => value << 1, Effect::Vulnerable => value << 1, Effect::Block => value >> 1, Effect::Amplify => value << 1, Effect::Curse => value << 1, Effect::Haste => value << 1, Effect::Slow => value >> 1, _ => { println!("{:?} does not have a mod effect", self); return value; }, } } pub fn category(&self) -> Category { match self { // physical Effect::Stun => Category::RedDebuff, Effect::Block => Category::RedBuff, Effect::Parry => Category::RedBuff, Effect::Bleed => Category::RedDebuff, Effect::Leech => Category::RedDebuff, Effect::Airborne => Category::RedDebuff, Effect::Untouchable => Category::RedBuff, Effect::Deadly => Category::RedBuff, Effect::Vulnerable => Category::RedDebuff, Effect::Fury => Category::RedBuff, Effect::Blind => Category::RedDebuff, Effect::Snare => Category::RedDebuff, Effect::Clutch => Category::RedBuff, Effect::Taunt => Category::RedBuff, Effect::Empower => Category::RedBuff, Effect::Strangle => Category::RedDebuff, Effect::Strangling => Category::RedBuff, // magic Effect::Hex => Category::BlueDebuff, Effect::Ruin => Category::BlueDebuff, Effect::Curse => Category::BlueDebuff, Effect::Banish => Category::BlueDebuff, // todo randomise Effect::Slow => Category::BlueDebuff, Effect::Haste => Category::BlueBuff, Effect::Reflect => Category::BlueBuff, Effect::Enslave => Category::BlueDebuff, Effect::Mesmerise => Category::BlueDebuff, Effect::Amplify => Category::BlueBuff, Effect::Silence => Category::BlueDebuff, // magic immunity Effect::Shield => Category::BlueBuff, Effect::Invert => Category::GreenBuff, // effects over time Effect::Triage => Category::BlueBuff, Effect::Decay => Category::BlueDebuff, Effect::Regen => Category::BlueBuff, Effect::Siphon => Category::BlueDebuff, Effect::SpeedSiphon => Category::BlueDebuff, Effect::SpeedIncrease => Category::BlueBuff, Effect::Ko => Category::Ko, } } pub fn duration(&self) -> u8 { match self { Effect::Stun => 2, Effect::Block => 1, Effect::Parry => 1, Effect::Clutch => 1, Effect::Reflect => 1, Effect::Strangle => 2, Effect::Strangling => 2, Effect::Vulnerable => 2, Effect::Snare => 2, Effect::Taunt => 1, Effect::Empower => 2, Effect::Invert => 1, Effect::Hex => 2, Effect::Ruin => 1, Effect::Curse => 2, Effect::Banish => 1, Effect::Slow => 2, Effect::Haste => 2, Effect::Amplify => 2, Effect::Silence => 2, Effect::Shield => 2, Effect::Triage => 3, Effect::Decay => 3, Effect::Siphon => 2, _ => { println!("{:?} does not have a duration", self); return 1; }, } } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Category { Red, RedDamage, RedDebuff, RedBuff, RedTick, Blue, BlueDamage, BlueDebuff, BlueBuff, BlueTick, Green, GreenDamage, GreenBuff, Ko, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Skill { Attack, // ----------------- // Nature // ----------------- Block, // reduce damage Parry, // avoid all damage Snare, Recharge, Reflect, Ruin, Slay, Clutch, Taunt, Toxic, Invert, Strangle, StrangleTick, Strike, Stun, // 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, // ----------------- // Purity // ----------------- Empower, Shield, Silence, Purify, Purge, // ----------------- // Chaos // ----------------- Banish, Hex, Haste, Slow, // used by tests, no cd, no damage TestTouch, TestStun, TestBlock, TestParry, TestSiphon, } impl Skill { pub fn base_cd(&self) -> Cooldown { match self { Skill::Attack => None, Skill::Strike => None, Skill::Block => None, // reduce damage Skill::Parry => None, // avoid all damage Skill::Snare => Some(1), Skill::Stun => Some(1), Skill::Heal => None, Skill::Triage => None, // hot Skill::TriageTick => None, Skill::Throw => Some(1), // no damage stun, adds vulnerable Skill::Blast => None, Skill::Amplify => Some(1), Skill::Invert => Some(1), Skill::Decay => None, // dot Skill::DecayTick => None, Skill::Siphon => Some(1), Skill::SiphonTick => None, Skill::Curse => Some(1), Skill::Empower => Some(1), Skill::Shield => None, Skill::Silence => Some(1), Skill::Purify => None, Skill::Purge => None, Skill::Banish => Some(1), Skill::Hex => None, Skill::Haste => None, Skill::Slow => None, Skill::Reflect => Some(2), Skill::Recharge => Some(2), Skill::Ruin => Some(3), Skill::Slay => None, Skill::Strangle => Some(2), Skill::StrangleTick => None, Skill::Clutch => Some(2), Skill::Taunt => Some(1), Skill::Toxic => Some(1), // ----------------- // Test // ----------------- 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::Strangle => Category::Red, Skill::StrangleTick => Category::Red, // ----------------- // Nature // ----------------- Skill::Block => Category::Red, // reduce damage Skill::Parry => Category::Red, // avoid all damage Skill::Snare => Category::Red, Skill::Clutch => Category::Red, Skill::Stun => Category::Red, // ----------------- // Technology // ----------------- // ----------------- // Preservation // ----------------- Skill::Heal => Category::Red, Skill::Triage => Category::Blue, // hot Skill::TriageTick => Category::BlueTick, // hot Skill::Throw => Category::Red, // no damage stun, adds vulnerable // ----------------- // Destruction // ----------------- Skill::Invert => Category::Green, Skill::Blast => 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, // ----------------- // Purity // ----------------- Skill::Empower => Category::Red, Skill::Shield => Category::Blue, Skill::Silence => Category::Blue, Skill::Purify => Category::Blue, Skill::Purge => Category::Blue, // ----------------- // Chaos // ----------------- Skill::Banish => Category::Blue, Skill::Hex => Category::Blue, // Skill::Lag => 2, // Skill::Haste => Category::Blue, Skill::Slow => Category::Blue, // WRONG Skill::Recharge => Category::Blue, Skill::Reflect => Category::Blue, Skill::Ruin => Category::Blue, Skill::Slay => Category::Blue, Skill::Taunt => Category::Red, Skill::Toxic => Category::Blue, // ----------------- // Test // ----------------- 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, _ => 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::SiphonTick => Var::from(Skill::Siphon).speed(), Skill::DecayTick => Var::from(Skill::Decay).speed(), Skill::TriageTick => Var::from(Skill::Triage).speed(), Skill::StrangleTick => Var::from(Skill::Strangle).speed(), _ => Var::from(*self).speed(), } } pub fn aoe(&self) -> bool { match self { Skill::Ruin => true, _ => false, } } pub fn resolve(&self, source: &mut Cryp, target: &mut Cryp) -> Resolutions { let mut rng = thread_rng(); let _base: u64 = rng.gen(); let mut results = vec![]; if let Some(disable) = source.disabled(*self) { results.push(Resolution::new(source, target).event(Event::Disable { disable })); return results; } if target.is_ko() { results.push(Resolution::new(source, target).event(Event::TargetKo)); return results; } if target.affected(Effect::Reflect) { // guard against overflow if source.affected(Effect::Reflect) { return results; } return self.resolve(target, source); } // match self.category() == Category::Red { // true => { // if let Some(evasion) = target.evade(*self) { // results.push(evasion); // return Event; // } // }, // false => (), // } // if target.is_ko() && !target.ko_logged { // self.log.push(format!("{:} KO", target.name)); // target.effects.clear(); // target.ko_logged = true; // } // if source.is_ko() && !source.ko_logged { // self.log.push(format!("{:} KO", source.name)); // source.effects.clear(); // source.ko_logged = true; // } match self { Skill::Amplify => amplify(source, target, results), // increase magic damage Skill::Attack => attack(source, target, results), Skill::Banish => banish(source, target, results), // TODO prevent all actions Skill::Blast => blast(source, target, results), Skill::Block => block(source, target, results), Skill::Curse => curse(source, target, results), Skill::Decay => decay(source, target, results), // dot Skill::DecayTick => decay_tick(source, target, results), // dot Skill::Empower => empower(source, target, results), // increased phys damage Skill::Haste => haste(source, target, results), // speed slow Skill::Heal => heal(source, target, results), Skill::Hex => hex(source, target, results), // todo prevent casting Skill::Invert => invert(source, target, results), // todo prevent casting Skill::Parry => parry(source, target, results), Skill::Purge => purge(source, target, results), // dispel all buffs Skill::Purify => purify(source, target, results), // dispel all debuffs Skill::Recharge => recharge(source, target, results), // target is immune to magic damage and fx Skill::Shield => shield(source, target, results), // target is immune to magic damage and fx Skill::Silence => silence(source, target, results), // target cannot cast spells Skill::Siphon => siphon(source, target, results), Skill::SiphonTick => siphon_tick(source, target, results), // hot Skill::Slow => slow(source, target, results), // speed slow Skill::Snare => snare(source, target, results), // TODO prevent physical moves Skill::Strike => strike(source, target, results), Skill::Stun => stun(source, target, results), Skill::Throw => throw(source, target, results), // no damage stun, adds vulnerable Skill::Triage => triage(source, target, results), // hot Skill::TriageTick => triage_tick(source, target, results), // hot Skill::Clutch => clutch(source, target, results), Skill::Strangle => strangle(source, target, results), Skill::StrangleTick => strangle_tick(source, target, results), Skill::Reflect => reflect(source, target, results), Skill::Ruin => ruin(source, target, results), Skill::Slay => unimplemented!(), Skill::Taunt => taunt(source, target, results), Skill::Toxic => unimplemented!(), // ----------------- // Test // ----------------- Skill::TestTouch => touch(source, target, results), Skill::TestStun => stun(source, target, results), Skill::TestBlock => block(source, target, results), Skill::TestParry => parry(source, target, results), Skill::TestSiphon => siphon(source, target, results), } } pub fn self_targeting(&self) -> bool { match self { Skill::Block => true, Skill::Parry => true, Skill::Clutch => true, Skill::TestBlock => true, Skill::TestParry => true, _ => false, } } pub fn defensive(&self) -> bool { match self { Skill::Heal | Skill::Triage | Skill::Empower | Skill::Purify | Skill::Parry | Skill::Block => true, _ => false, } } } fn touch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { results.push(Resolution::new(source, target).event(target.deal_red_damage(Skill::TestTouch, 0))); return results; } fn attack(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amount = source.red_damage(); results.push(Resolution::new(source, target).event(target.deal_red_damage(Skill::Attack, amount))); return results; } fn stun(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Stun, duration: Effect::Stun.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Stun, effect))); return results; } fn clutch(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Clutch, duration: Effect::Clutch.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Clutch, effect))); return results; } fn taunt(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Taunt, duration: Effect::Taunt.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Taunt, effect))); return results; } fn throw(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let stun = CrypEffect { effect: Effect::Stun, duration: Effect::Stun.duration(), tick: None }; let vulnerable = CrypEffect { effect: Effect::Vulnerable, duration: Effect::Vulnerable.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Throw, stun))); results.push(Resolution::new(source, target).event(target.add_effect(Skill::Throw, vulnerable))); return results; } fn strangle(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let target_stun = CrypEffect { effect: Effect::Strangle, duration: Effect::Strangle.duration(), tick: Some(Cast::new_tick(source, target, Skill::StrangleTick)) }; let attacker_immunity = CrypEffect { effect: Effect::Strangling, duration: Effect::Strangling.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Strangle, target_stun))); results.push(Resolution::new(source, source).event(source.add_effect(Skill::Strangle, attacker_immunity))); return strangle_tick(source, target, results); } fn strangle_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amount = source.red_damage(); results.push(Resolution::new(source, target).event(target.deal_red_damage(Skill::StrangleTick, amount))); // 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) -> Resolutions { let block = CrypEffect { effect: Effect::Block, duration: Effect::Block.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Block, block))); return results; } fn parry(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Parry, duration: Effect::Parry.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Parry, effect))); return results; } fn snare(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let snare = CrypEffect { effect: Effect::Snare, duration: Effect::Snare.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Snare, snare))); return results; } fn empower(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let empower = CrypEffect { effect: Effect::Empower, duration: Effect::Empower.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Empower, empower))); return results; } fn heal(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amount = source.green_damage(); results.push(Resolution::new(source, target).event(target.deal_green_damage(Skill::Heal, amount))); return results; } fn triage(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Triage, duration: Effect::Triage.duration(), tick: Some(Cast::new_tick(source, target, Skill::TriageTick)), }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Triage, effect))); return triage_tick(source, target, results); } fn triage_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amount = source.green_damage().wrapping_div(2); results.push(Resolution::new(source, target).event(target.deal_green_damage(Skill::TriageTick, amount))); return results; } fn blast(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amount = source.blue_damage(); results.push(Resolution::new(source, target).event(target.deal_blue_damage(Skill::Blast, amount))); return results; } fn amplify(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amplify = CrypEffect { effect: Effect::Amplify, duration: Effect::Amplify.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Amplify, amplify))); return results;; } fn haste(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Haste, duration: Effect::Haste.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Haste, effect))); return results;; } fn slow(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Slow, duration: Effect::Slow.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Slow, effect))); return results;; } fn decay(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let decay = CrypEffect { effect: Effect::Decay, duration: Effect::Decay.duration(), tick: Some(Cast::new_tick(source, target, Skill::DecayTick)), }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Decay, decay))); return decay_tick(source, target, results); } fn decay_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amount = source.blue_damage(); results.push(Resolution::new(source, target).event(target.deal_blue_damage(Skill::DecayTick, amount))); return results; } fn ruin(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Ruin, duration: Effect::Ruin.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Ruin, effect))); return results;; } fn hex(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let hex = CrypEffect { effect: Effect::Hex, duration: Effect::Hex.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Hex, hex))); return results;; } fn curse(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let curse = CrypEffect { effect: Effect::Curse, duration: Effect::Curse.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Curse, curse))); return results;; } fn invert(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Invert, duration: Effect::Invert.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Invert, effect))); return results;; } fn reflect(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let effect = CrypEffect { effect: Effect::Reflect, duration: Effect::Reflect.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Reflect, effect))); return results;; } fn recharge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { results.push(Resolution::new(source, target).event(target.recharge())); return results; } fn siphon(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let siphon = CrypEffect { effect: Effect::Siphon, duration: Effect::Siphon.duration(), tick: Some(Cast::new_tick(source, target, Skill::SiphonTick)), }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Siphon, siphon))); return siphon_tick(source, target, results); } fn siphon_tick(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let amount = source.blue_damage(); let siphon_damage = target.deal_blue_damage(Skill::SiphonTick, amount); results.push(Resolution::new(source, target).event(siphon_damage.clone())); match siphon_damage { Event::Damage { amount, mitigation: _, category: _, } => { results.push(Resolution::new(source, source).event(source.deal_green_damage(Skill::Heal, amount))); }, _ => panic!("siphon tick damage not dealt {:?}", siphon_damage), } return results; } fn shield(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let shield = CrypEffect { effect: Effect::Shield, duration: Effect::Shield.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Shield, shield))); return results; } fn silence(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let silence = CrypEffect { effect: Effect::Silence, duration: Effect::Silence.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Silence, silence))); return results; } fn purge(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { for (i, ce) in target.effects.clone().iter_mut().enumerate() { if ce.effect.category() == Category::BlueBuff { 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) -> Resolutions { for (i, ce) in target.effects.clone().iter_mut().enumerate() { if ce.effect.category() == Category::BlueDebuff { target.effects.remove(i); results.push(Resolution::new(source, target).event(Event::Removal { effect: ce.effect })); } } return results; } fn banish(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let banish = CrypEffect { effect: Effect::Banish, duration: Effect::Banish.duration(), tick: None }; results.push(Resolution::new(source, target).event(target.add_effect(Skill::Banish, banish))); return results; } fn strike(source: &mut Cryp, target: &mut Cryp, mut results: Resolutions) -> Resolutions { let _amount = source.red_damage(); results.push(Resolution::new(source, target).event(target.deal_red_damage(Skill::Attack, u64::max_value()))); 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![]); } #[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![]); 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.hp() == y.hp().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.hp.force(500); block(&mut y.clone(), &mut y, vec![]); assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); let mut results = attack(&mut x, &mut y, vec![]); let Resolution { source: _, target: _, event } = results.remove(0); match event { Event::Damage { amount, mitigation: _, category: _ } => 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(u64::max_value()); clutch(&mut y.clone(), &mut y, vec![]); assert!(y.affected(Effect::Clutch)); let mut results = attack(&mut x, &mut y, vec![]); assert!(y.hp() == 1); let Resolution { source: _, target: _, event } = results.remove(0); match event { Event::Damage { amount, mitigation: _, category: _ } => assert_eq!(amount, 1023), _ => panic!("not damage"), }; } #[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_shield.force(64); y.red_shield.reduce(64); x.red_damage.force(256 + 64); invert(&mut y.clone(), &mut y, vec![]); assert!(y.affected(Effect::Invert)); // heal should deal green damage heal(&mut x, &mut y, vec![]); assert!(y.hp() == 768); // attack should heal and recharge red shield let mut results = attack(&mut x, &mut y, vec![]); assert!(y.hp() == 1024); let Resolution { source: _, target: _, event } = results.remove(0); match event { Event::Inversion { damage: _, healing: _, recharge, category: _ } => assert_eq!(recharge, 64), _ => panic!("not 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![]); assert!(y.affected(Effect::Reflect)); let mut results = Skill::Attack.resolve(&mut x, &mut y); assert!(x.hp() == 768); let Resolution { source: _, target: _, event } = results.remove(0); match event { Event::Damage { amount, mitigation: _, category: _ } => assert_eq!(amount, 256), _ => panic!("not damage"), }; } #[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_shield.force(0); y.blue_shield.force(0); y.deal_red_damage(Skill::Attack, 5); let prev_hp = y.hp(); let _results = triage(&mut x, &mut y, vec![]); assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); assert!(y.hp() > 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_shield.force(50); y.blue_shield.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![]); let Resolution { source: _, target: _, event } = results.remove(0); match event { Event::Recharge { red, blue } => { 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![]); 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![]); assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); assert_eq!(x.blue_damage(), 100); } #[test] fn purify_test() { let mut x = Cryp::new() .named(&"muji".to_string()); decay(&mut x.clone(), &mut x, vec![]); assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); purify(&mut x.clone(), &mut x, vec![]); assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); } } // pub enum Skill { // Attack, // // ----------------- // // Nature // // ----------------- // Block, // reduce damage // Parry, // avoid all damage // Snare, // Paralyse, // Strangle, // physical dot and disable // Strike, // Stun, // // Evade, // actively evade // // ----------------- // // Technology // // ----------------- // Replicate, // Swarm, // Orbit, // Repair, // Scan, // track? // // ----------------- // // Nonviolence // // ----------------- // Heal, // Triage, // hot // TriageTick, // Throw, // no damage stun, adds vulnerable // Charm, // Calm, // Rez, // // Sleep, // // Nightmare, // // ------------------- // // Destruction // // ------------------- // Blast, // Amplify, // Decay, // dot // DecayTick, // dot // Siphon, // SiphonTick, // Curse, // Plague, // aoe dot // Ruin, // aoe // // ----------------- // // Purity // // ----------------- // Empower, // Slay, // Shield, // Silence, // Inquiry, // Purify, // Purge, // // Precision, // // ----------------- // // Chaos // // ----------------- // Banish, // Hex, // Fear, // Taunt, // Pause, // speed slow // Haste, // Slow, // // used by tests, no cd, no damage // TestTouch, // TestStun, // TestBlock, // TestParry, // TestSiphon, // }