From 8cb335fcc7a4b668a74cb78abbe0b19366d29c8b Mon Sep 17 00:00:00 2001 From: ntr Date: Tue, 3 Dec 2019 10:21:22 +1000 Subject: [PATCH] break stuff --- core/fixme.md | 4 + core/src/construct.rs | 97 +- core/src/effect.rs | 10 +- core/src/game.rs | 1136 +++++++++---------- core/src/instance.rs | 4 +- core/src/item.rs | 9 +- core/src/player.rs | 6 +- core/src/skill.rs | 2462 +++++++++++++++++++++-------------------- core/src/spec.rs | 12 +- core/src/util.rs | 6 +- 10 files changed, 1906 insertions(+), 1840 deletions(-) create mode 100644 core/fixme.md diff --git a/core/fixme.md b/core/fixme.md new file mode 100644 index 00000000..3f8b070b --- /dev/null +++ b/core/fixme.md @@ -0,0 +1,4 @@ +# FIXME + +aoe event +cooldown checking -> go through round and find all casters \ No newline at end of file diff --git a/core/src/construct.rs b/core/src/construct.rs index 7deb7abb..02beb0e9 100644 --- a/core/src/construct.rs +++ b/core/src/construct.rs @@ -4,8 +4,9 @@ use rand::prelude::*; use failure::Error; use failure::err_msg; -use skill::{Skill, Cast, Immunity, Disable, Event}; -use effect::{Cooldown, Effect, Colour}; +use skill::{Skill, Cast, Immunity, Disable, EventVariant}; +use game::{Colour}; +use effect::{Cooldown, Effect}; use spec::{Spec}; use item::{Item}; @@ -60,10 +61,10 @@ impl ConstructSkill { #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum EffectMeta { Skill(Skill), - TickAmount(u64), - AddedDamage(u64), + TickAmount(usize), + AddedDamage(usize), LinkTarget(Uuid), - Multiplier(u64), + Multiplier(usize), } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] @@ -93,7 +94,7 @@ impl ConstructEffect { self.duration } - pub fn get_multiplier(&self) -> u64 { + pub fn get_multiplier(&self) -> usize { match self.meta { Some(EffectMeta::Multiplier(s)) => s, _ => 0 @@ -129,14 +130,14 @@ pub enum Stat { #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct ConstructStat { - base: u64, - value: u64, - max: u64, + base: usize, + value: usize, + max: usize, pub stat: Stat, } impl ConstructStat { - // pub fn set(&mut self, v: u64, specs: &Vec) -> &mut ConstructStat { + // pub fn set(&mut self, v: usize, specs: &Vec) -> &mut ConstructStat { // self.base = v; // self.recalculate(specs) // } @@ -157,12 +158,12 @@ impl ConstructStat { self } - pub fn reduce(&mut self, amt: u64) -> &mut ConstructStat { + pub fn reduce(&mut self, amt: usize) -> &mut ConstructStat { self.value = self.value.saturating_sub(amt); self } - pub fn increase(&mut self, amt: u64) -> &mut ConstructStat { + pub fn increase(&mut self, amt: usize) -> &mut ConstructStat { self.value = *[ self.value.saturating_add(amt), self.max @@ -171,7 +172,7 @@ impl ConstructStat { self } - pub fn force(&mut self, v: u64) -> &mut ConstructStat { + pub fn force(&mut self, v: usize) -> &mut ConstructStat { self.base = v; self.value = v; self.max = v; @@ -470,7 +471,7 @@ impl Construct { } // Stats - pub fn red_power(&self) -> u64 { + pub fn red_power(&self) -> usize { let red_power_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::RedPower)) .map(|e| (e.effect, e.meta)) @@ -481,7 +482,7 @@ impl Construct { return modified_red_power; } - pub fn blue_power(&self) -> u64 { + pub fn blue_power(&self) -> usize { let blue_power_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::BluePower)) .map(|e| (e.effect, e.meta)) @@ -492,7 +493,7 @@ impl Construct { return modified_blue_power; } - pub fn green_power(&self) -> u64 { + pub fn green_power(&self) -> usize { let green_power_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::GreenPower)) .map(|e| (e.effect, e.meta)) @@ -503,8 +504,8 @@ impl Construct { return modified_green_power; } - pub fn skill_speed(&self, s: Skill) -> u64 { - self.speed().saturating_mul(s.speed() as u64) + pub fn skill_speed(&self, s: Skill) -> usize { + self.speed().saturating_mul(s.speed() as usize) } // todo complete with specs @@ -512,7 +513,7 @@ impl Construct { s.aoe() } - pub fn speed(&self) -> u64 { + pub fn speed(&self) -> usize { let speed_mods = self.effects.iter() .filter(|e| e.effect.modifications().contains(&Stat::Speed)) .map(|e| (e.effect, e.meta)) @@ -523,32 +524,32 @@ impl Construct { return modified_speed; } - pub fn red_life(&self) -> u64 { + pub fn red_life(&self) -> usize { self.red_life.value } - pub fn blue_life(&self) -> u64 { + pub fn blue_life(&self) -> usize { self.blue_life.value } - pub fn green_life(&self) -> u64 { + pub fn green_life(&self) -> usize { self.green_life.value } - fn reduce_green_life(&mut self, amount: u64) { + fn reduce_green_life(&mut self, amount: usize) { self.green_life.reduce(amount); if self.affected(Effect::Sustain) && self.green_life() == 0 { self.green_life.value = 1; } } - pub fn recharge(&mut self, skill: Skill, red_amount: u64, blue_amount: u64) -> Vec { + pub fn recharge(&mut self, skill: Skill, red_amount: usize, blue_amount: usize) -> Vec { let mut events = vec![]; // Should red type immunity block recharge??? if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -567,7 +568,7 @@ impl Construct { let blue = new_blue_life - current_blue_life; if red != 0 || blue != 0 { - events.push(Event::Recharge { red, blue, skill }); + events.push(EventVariant::Recharge { red, blue, skill }); } }, true => { @@ -593,7 +594,7 @@ impl Construct { self.reduce_green_life(red_remainder); let red_damage_amount = red_current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: red_damage_amount, mitigation: red_mitigation, @@ -622,7 +623,7 @@ impl Construct { self.reduce_green_life(blue_remainder); let blue_damage_amount = blue_current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: blue_damage_amount, mitigation: blue_mitigation, @@ -634,11 +635,11 @@ impl Construct { return events; } - pub fn deal_green_damage(&mut self, skill: Skill, amount: u64) -> Vec { + pub fn deal_green_damage(&mut self, skill: Skill, amount: usize) -> Vec { let mut events = vec![]; if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -660,21 +661,21 @@ impl Construct { let healing = new_green_life - current_green_life; let overhealing = modified_power - healing; - events.push(Event::Healing { + events.push(EventVariant::Healing { skill, amount: healing, overhealing, }); }, true => { - // events.push(Event::Inversion { skill }); + // events.push(EventVariant::Inversion { skill }); // there is no green shield (yet) let current_green_life = self.green_life(); self.reduce_green_life(modified_power); let delta = current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: delta, mitigation: 0, @@ -686,12 +687,12 @@ impl Construct { return events; } - pub fn deal_red_damage(&mut self, skill: Skill, amount: u64) -> Vec { + pub fn deal_red_damage(&mut self, skill: Skill, amount: usize) -> Vec { let mut events = vec![]; if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -721,7 +722,7 @@ impl Construct { self.reduce_green_life(remainder); let delta = current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: delta, mitigation, @@ -729,7 +730,7 @@ impl Construct { }); }, true => { - // events.push(Event::Inversion { skill }); + // events.push(EventVariant::Inversion { skill }); let current_green_life = self.green_life(); self.green_life.increase(modified_power); @@ -742,7 +743,7 @@ impl Construct { let recharge = self.red_life.value - current_life; if healing > 0 { - events.push(Event::Healing { + events.push(EventVariant::Healing { skill, amount: healing, overhealing: overhealing - recharge, @@ -750,7 +751,7 @@ impl Construct { } if recharge > 0 { - events.push(Event::Recharge { red: recharge, blue: 0, skill }); + events.push(EventVariant::Recharge { red: recharge, blue: 0, skill }); } } }; @@ -758,12 +759,12 @@ impl Construct { return events; } - pub fn deal_blue_damage(&mut self, skill: Skill, amount: u64) -> Vec { + pub fn deal_blue_damage(&mut self, skill: Skill, amount: usize) -> Vec { let mut events = vec![]; if let Some(immunity) = self.immune(skill) { if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); + events.push(EventVariant::Immunity { skill, immunity }); } return events; } @@ -789,7 +790,7 @@ impl Construct { self.reduce_green_life(remainder); let delta = current_green_life - self.green_life(); - events.push(Event::Damage { + events.push(EventVariant::Damage { skill, amount: delta, mitigation, @@ -797,7 +798,7 @@ impl Construct { }); }, true => { - // events.push(Event::Inversion { skill }); + // events.push(EventVariant::Inversion { skill }); let current_green_life = self.green_life(); self.green_life.increase(modified_power); @@ -810,7 +811,7 @@ impl Construct { let recharge = self.blue_life.value - current_life; if healing > 0 { - events.push(Event::Healing { + events.push(EventVariant::Healing { skill, amount: healing, overhealing, @@ -818,7 +819,7 @@ impl Construct { } if recharge > 0 { - events.push(Event::Recharge { red: 0, blue: recharge, skill }); + events.push(EventVariant::Recharge { red: 0, blue: recharge, skill }); } } }; @@ -828,7 +829,7 @@ impl Construct { pub fn add_effect(&mut self, skill: Skill, effect: ConstructEffect) -> Event { if let Some(immunity) = self.immune(skill) { - return Event::Immunity { + return EventVariant::Immunity { skill, immunity, }; @@ -846,7 +847,7 @@ impl Construct { } // todo modified durations cause of buffs - let result = Event::Effect { + let result = EventVariant::Effect { effect: effect.effect, duration: effect.duration, construct_effects: self.effects.clone(), @@ -867,7 +868,7 @@ impl Construct { // info!("{:} < {:?}", roll, evasion_rating); // match roll < evasion_rating { - // true => Some(Event::Evasion { + // true => Some(EventVariant::Evasion { // skill, // evasion_rating: evasion_rating, // }), diff --git a/core/src/effect.rs b/core/src/effect.rs index e1a87604..b1cb1ca4 100644 --- a/core/src/effect.rs +++ b/core/src/effect.rs @@ -1,4 +1,5 @@ use construct::{Stat, EffectMeta}; +use game::{Colour}; use skill::{Skill}; use util::{IntPct}; @@ -123,7 +124,7 @@ impl Effect { } } - pub fn apply(&self, value: u64, meta: Option) -> u64 { + pub fn apply(&self, value: usize, meta: Option) -> usize { match self { Effect::Amplify | Effect::Vulnerable | @@ -200,10 +201,3 @@ impl Effect { } } } - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum Colour { - Red, - Blue, - Green, -} diff --git a/core/src/game.rs b/core/src/game.rs index 2f9274a4..6ed1673a 100644 --- a/core/src/game.rs +++ b/core/src/game.rs @@ -9,7 +9,7 @@ use chrono::Duration; use failure::Error; use failure::err_msg; -use construct::{Construct}; +use construct::{Construct, Stat}; use skill::{Skill, Cast, Resolution, Event, resolve}; use effect::{Effect}; use player::{Player}; @@ -23,6 +23,28 @@ pub enum Phase { Finished, } +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Value { + Stat { construct: Uuid, stat: Stat, mult: usize }, + Fixed { amount: usize }, + Cooldowns { construct: Uuid }, + // Skills { construct: Uuid, colour: Colour }, +} + +#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] +pub enum Colour { + Red, + Blue, + Green, +} + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub enum Action { + Damage { construct: Uuid, skill: Skill, values: Vec, colour: Colour }, + // Recharge { skill: Skill, red: usize, blue: usize }, + // Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, +} + #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Game { pub id: Uuid, @@ -31,7 +53,7 @@ pub struct Game { pub players: Vec, pub phase: Phase, pub stack: Vec, - pub resolved: Vec, + pub resolutions: Vec>, pub instance: Option, time_control: TimeControl, phase_start: DateTime, @@ -47,7 +69,7 @@ impl Game { players: vec![], phase: Phase::Start, stack: vec![], - resolved: vec![], + resolutions: vec![], instance: None, time_control: TimeControl::Standard, phase_end: None, @@ -61,7 +83,7 @@ impl Game { .collect(); self.stack - .retain(|s| s.source_player_id == account); + .retain(|s| s.player == account); self } @@ -259,7 +281,7 @@ impl Game { self } - fn add_skill(&mut self, player_id: Uuid, source_construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result<&mut Game, Error> { + fn add_skill(&mut self, player_id: Uuid, source: Uuid, target: Uuid, skill: Skill) -> Result<&mut Game, Error> { // check player in game self.player_by_id(player_id)?; @@ -269,7 +291,7 @@ impl Game { // target checks { - let target = match self.construct_by_id(target_construct_id) { + let target = match self.construct_by_id(target) { Some(c) => c, None => return Err(err_msg("target construct not in game")), }; @@ -282,7 +304,7 @@ impl Game { // construct checks { - let construct = match self.construct_by_id(source_construct_id) { + let construct = match self.construct_by_id(source) { Some(c) => c, None => return Err(err_msg("construct not in game")), }; @@ -307,11 +329,11 @@ impl Game { } // replace construct skill - if let Some(s) = self.stack.iter_mut().position(|s| s.source_construct_id == source_construct_id) { + if let Some(s) = self.stack.iter_mut().position(|s| s.source == source) { self.stack.remove(s); } - let skill = Cast::new(source_construct_id, player_id, target_construct_id, skill); + let skill = Cast::new(source, player_id, target, skill); self.stack.push(skill); return Ok(self); @@ -359,7 +381,7 @@ impl Game { return Err(err_msg("game not in skill phase")); } let mut game_state = self.clone(); - self.stack.retain(|s| game_state.construct_by_id(s.source_construct_id).unwrap().account != player_id); + self.stack.retain(|s| game_state.construct_by_id(s.source).unwrap().account != player_id); return Ok(self); } @@ -381,21 +403,18 @@ impl Game { // // for every player // .all(|t| self.stack.iter() // // the number of skills they have cast - // .filter(|s| s.source_player_id == t.id).collect::>() + // .filter(|s| s.player == t.id).collect::>() // // should equal the number required this turn // .len() == t.skills_required() // ) } - // requires no input - // just do it fn resolve_phase_start(mut self) -> Game { if self.phase != Phase::Skill { panic!("game not in skill phase"); } - assert!(self.skill_phase_finished()); - self.phase = Phase::Resolve; + self.resolutions.push(vec![]); // self.log.push("".to_string()); self.resolve_stack() @@ -406,7 +425,7 @@ impl Game { sorted.iter_mut() .for_each(|s| { if !s.skill.is_tick() { - let caster = self.construct_by_id(s.source_construct_id).unwrap(); + let caster = self.construct_by_id(s.source).unwrap(); let speed = caster.skill_speed(s.skill); s.speed = speed; } @@ -419,31 +438,6 @@ impl Game { self } - fn construct_aoe_targets(&self, construct_id: Uuid) -> Vec { - self.players.iter() - .find(|t| t.constructs.iter().any(|c| c.id == construct_id)) - .unwrap() - .constructs - .iter() - .map(|c| c.id) - .collect() - } - - pub fn get_targets(&self, skill: Skill, source: &Construct, target_construct_id: Uuid) -> Vec { - let target_player = self.players.iter() - .find(|t| t.constructs.iter().any(|c| c.id == target_construct_id)) - .unwrap(); - - if let Some(t) = target_player.intercepting() { - return vec![t.id]; - } - - match source.skill_is_aoe(skill) { - true => self.construct_aoe_targets(target_construct_id), - false => vec![target_construct_id], - } - } - fn resolve_stack(mut self) -> Game { if self.phase != Phase::Resolve { panic!("game not in Resolve phase"); @@ -471,18 +465,13 @@ impl Game { while let Some(cast) = self.stack.pop() { // info!("{:} casts ", cast); - let mut resolutions = vec![]; - resolutions = resolve(&cast, &mut self, resolutions); - r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); + resolve(&mut self, cast); + // r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); - // if theres no resolution events, the skill didn't trigger (disable etc) - if resolutions.len() > 0 && cast.used_cooldown() { - casters.push(cast); - } - - self.resolved.append(&mut resolutions); - - // println!("{:?}", self.resolved); + // if theres no resolution resolutions, the skill didn't trigger (disable etc) + // if resolutions.len() > 0 && cast.used_cooldown() { + // casters.push(cast); + // } // sort the stack again in case speeds have changed self.stack_sort_speed(); @@ -500,7 +489,32 @@ impl Game { self.skill_phase_start(r_animation_ms) } - fn progress_durations(&mut self, resolved: &Vec) -> &mut Game { + fn resolution_add(&mut self, mut resolutions: Vec) -> &mut Game { + self.resolutions.last_mut().unwrap().append(&mut resolutions); + self + } + + pub fn actions(&mut self, actions: Vec) -> &mut Game { + for action in actions { + match action { + Action::Damage { construct, skill, values, colour } => self.damage(construct, skill, values, colour), + }; + } + + self + } + + fn damage(&mut self, construct: Uuid, skill: Skill, values: Vec, colour: Colour) -> &mut Game { + let target = self.construct_by_id(construct).unwrap(); + let resolutions = match colour { + _ => target.deal_red_damage(skill, 128) // fixme unwrap + }; + + self.resolution_add(resolutions); + self + } + + fn progress_durations(&mut self, resolutions: &Vec) -> &mut Game { for mut construct in self.all_constructs() { // info!("progressing durations for {:}", construct.name); @@ -508,7 +522,7 @@ impl Game { continue; } - match resolved.iter().find(|s| s.source_construct_id == construct.id) { + match resolutions.iter().find(|s| s.source == construct.id) { Some(skill) => { construct.skill_set_cd(skill.skill); }, None => { construct.reduce_cooldowns(); }, }; @@ -521,7 +535,7 @@ impl Game { self } - // fn log_resolution(&mut self, speed: u64, resolution: &Resolution) -> &mut Game { + // fn log_resolution(&mut self, speed: usize, resolution: &Resolution) -> &mut Game { // let Resolution { source, target, event, stages: _ } = resolution; // match event { // Event::Ko { skill: _ }=> @@ -634,7 +648,7 @@ impl Game { // player.forfeit(); // info!("upkeep: {:} forfeited", player.name); // //todo - // // self.resolved.push(forfeit) + // // self.resolutions.push(forfeit) // // self.log.push(format!("{:} forfeited.", player.name)); // } } @@ -747,257 +761,8 @@ mod tests { return game.start(); } - #[test] - fn phase_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - - game = game.resolve_phase_start(); - - assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); - - return; - } - - #[test] - fn stun_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - - // assert!(game.player_by_id(y_player.id).constructs[0].is_stunned()); - // assert!(game.player_by_id(y_player.id).skills_required() == 0); - } - - #[test] - fn ko_resolution_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); - game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().speed.force(1000000000); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - // just in case - // remove all mitigation - game.player_by_id(x_player.id).unwrap().construct_by_id(x_construct.id).unwrap().red_life.force(0); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned()); - assert!(game.phase == Phase::Finished); - } - - #[test] - fn cooldown_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - game = game.resolve_phase_start(); - - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); - - // second round - // now we block and it should go back on cd - // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - game = game.resolve_phase_start(); - - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_none()); - assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); - } - - #[test] - fn sleep_cooldown_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - - for _n in 1..10 { - // should auto progress back to skill phase - assert!(game.phase == Phase::Skill); - - // Sleep 2T CD - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - // Sleep 1T CD - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); - // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - // Sleep 0T CD (we use it here) - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_none()); - - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Sleep).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - // Sleep back to 2T CD - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); - } - - } - - #[test] - fn counter_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { - game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); - } - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Counter).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Counter).unwrap(); - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - - game = game.resolve_phase_start(); - - // don't get stunned but not really stunning ¯\_(ツ)_/¯ - assert!(game.player_by_id(x_player.id).unwrap().constructs[0].is_stunned() == false); - // riposte - assert_eq!(game.player_by_id(y_player.id).unwrap().constructs[0].green_life(), ( - y_construct.green_life() + y_construct.red_life() - x_construct.red_power().pct(Skill::CounterAttack.multiplier()))); - } - - #[test] - fn electrify_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - // one shot the target construct (should still get debuffed) - game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Electrify); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Electrify).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - // apply buff - game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Electrify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - // game = game.resolve_phase_start(); - // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Electric)); - - // attack and receive debuff - game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Electrocute)); - } - // #[test] - // fn link_test() { + // fn phase_test() { // let mut game = create_test_game(); // let x_player = game.players[0].clone(); @@ -1006,48 +771,466 @@ mod tests { // let x_construct = x_player.constructs[0].clone(); // let y_construct = y_player.constructs[0].clone(); - // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Link); + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Link).is_some() { + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + + // game = game.resolve_phase_start(); + + // assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); + + // return; + // } + + // #[test] + // fn stun_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + + // // assert!(game.player_by_id(y_player.id).constructs[0].is_stunned()); + // // assert!(game.player_by_id(y_player.id).skills_required() == 0); + // } + + // #[test] + // fn ko_resolution_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); + // game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().speed.force(1000000000); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // // just in case + // // remove all mitigation + // game.player_by_id(x_player.id).unwrap().construct_by_id(x_construct.id).unwrap().red_life.force(0); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // assert!(!game.player_by_id(y_player.id).unwrap().constructs[0].is_stunned()); + // assert!(game.phase == Phase::Finished); + // } + + // #[test] + // fn cooldown_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_some()); + + // // second round + // // now we block and it should go back on cd + // // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Stun).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Stun).is_none()); + // assert!(game.player_by_id(y_player.id).unwrap().constructs[0].skill_on_cd(Skill::Block).is_none()); + // } + + // #[test] + // fn sleep_cooldown_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + + // for _n in 1..10 { + // // should auto progress back to skill phase + // assert!(game.phase == Phase::Skill); + + // // Sleep 2T CD + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // // Sleep 1T CD + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); + // // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Attack).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // // Sleep 0T CD (we use it here) + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_none()); + + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Sleep).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // // Sleep back to 2T CD + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Decay).is_none()); + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].skill_on_cd(Skill::Sleep).is_some()); + // } + + // } + + // #[test] + // fn counter_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Stun).is_some() { + // game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); + // } + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Counter).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Counter).unwrap(); + // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // // don't get stunned but not really stunning ¯\_(ツ)_/¯ + // assert!(game.player_by_id(x_player.id).unwrap().constructs[0].is_stunned() == false); + // // riposte + // assert_eq!(game.player_by_id(y_player.id).unwrap().constructs[0].green_life(), ( + // y_construct.green_life() + y_construct.red_life() - x_construct.red_power().pct(Skill::CounterAttack.multiplier()))); + // } + + // #[test] + // fn electrify_test() { + // let mut game = create_test_game(); + + // let x_player = game.players[0].clone(); + // let y_player = game.players[1].clone(); + + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = y_player.constructs[0].clone(); + + // // one shot the target construct (should still get debuffed) + // game.player_by_id(y_player.id).unwrap().construct_by_id(y_construct.id).unwrap().red_power.force(1000000000); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Electrify); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Electrify).is_some() { // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); // } // // apply buff - // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Link).unwrap(); + // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Electrify).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); - // game = game.resolve_phase_start(); - // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Link)); + // // game = game.resolve_phase_start(); + // // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Electric)); - // let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - // match event { - // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link), - // _ => panic!("not siphon"), - // }; - - // let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - // match event { - // Event::Recharge { red: _, blue: _, skill: _ } => (), - // _ => panic!("link result was not recharge"), - // } - - // // attack and receive link hit + // // attack and receive debuff // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); // game = game.resolve_phase_start(); - // let Resolution { source: _, target, event, stages: _ } = game.resolved.pop().unwrap(); - // assert_eq!(target.id, y_construct.id); - // match event { - // Event::Damage { amount, skill: _, mitigation: _, colour: _} => - // assert_eq!(amount, x_construct.red_power().pct(Skill::Attack.multiplier()) >> 1), - // _ => panic!("not damage link"), - // }; + // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Electrocute)); + // } + + // // #[test] + // // fn link_test() { + // // let mut game = create_test_game(); + + // // let x_player = game.players[0].clone(); + // // let y_player = game.players[1].clone(); + + // // let x_construct = x_player.constructs[0].clone(); + // // let y_construct = y_player.constructs[0].clone(); + + // // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Link); + + // // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Link).is_some() { + // // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // // } + + // // // apply buff + // // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Link).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + // // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Link)); + + // // let Resolution { source: _, target: _, event, stages: _ } = game.resolutions.last.unwrap().pop().unwrap(); + // // match event { + // // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link), + // // _ => panic!("not siphon"), + // // }; + + // // let Resolution { source: _, target: _, event, stages: _ } = game.resolutions.last.unwrap().pop().unwrap(); + // // match event { + // // Event::Recharge { red: _, blue: _, skill: _ } => (), + // // _ => panic!("link result was not recharge"), + // // } + + // // // attack and receive link hit + // // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + + // // let Resolution { source: _, target, event, stages: _ } = game.resolutions.last.unwrap().pop().unwrap(); + // // assert_eq!(target.id, y_construct.id); + // // match event { + // // Event::Damage { amount, skill: _, mitigation: _, colour: _} => + // // assert_eq!(amount, x_construct.red_power().pct(Skill::Attack.multiplier()) >> 1), + // // _ => panic!("not damage link"), + // // }; + // // } + + // // #[test] + // // fn absorb_test() { + // // let mut game = create_test_game(); + + // // let x_player = game.players[0].clone(); + // // let y_player = game.players[1].clone(); + + // // let x_construct = x_player.constructs[0].clone(); + // // let y_construct = y_player.constructs[0].clone(); + + // // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Absorb); + + // // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Absorb).is_some() { + // // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // // } + + // // // apply buff + // // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Absorb).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + // // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Absorb)); + + // // // attack and receive debuff + // // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::TestAttack).unwrap(); + // // game.player_ready(x_player.id).unwrap(); + // // game.player_ready(y_player.id).unwrap(); + // // game = game.resolve_phase_start(); + + // // info!("{:#?}", game); + // // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Absorption)); + // // } + + // #[test] + // fn aoe_test() { + // let mut game = create_2v2_test_game(); + + // let i_player = game.players[0].clone(); + // let x_player = game.players[1].clone(); + + // let i_construct = i_player.constructs[0].clone(); + // let j_construct = i_player.constructs[1].clone(); + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = x_player.constructs[1].clone(); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Ruin); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Ruin).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Ruin).unwrap(); + // game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(i_player.id).unwrap(); + // game.player_ready(x_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + // let ruins = game.resolutions + // .last().unwrap() + // .into_iter() + // .filter(|r| { + // let Resolution { source, target: _, event, stages: _ } = r; + // match source.id == x_construct.id { + // true => match event { + // Event::Effect { effect, duration, skill: _, construct_effects: _ } => { + // assert!(*effect == Effect::Stun); + // assert!(*duration == 1); + // true + // }, + // Event::AoeSkill { skill: _ } => false, + // Event::Damage { amount: _, mitigation: _, colour: _, skill: _ } => false, + // _ => panic!("ruin result not effect {:?}", event), + // } + // false => false, + // } + // }) + // .count(); + + // assert!(ruins == 2); // } // #[test] - // fn absorb_test() { + // fn intercept_test() { + // let mut game = create_2v2_test_game(); + + // let i_player = game.players[0].clone(); + // let x_player = game.players[1].clone(); + + // let i_construct = i_player.constructs[0].clone(); + // let j_construct = i_player.constructs[1].clone(); + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = x_player.constructs[1].clone(); + + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Intercept); + + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Intercept).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Intercept).unwrap(); + // game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); + + // game.player_ready(i_player.id).unwrap(); + // game.player_ready(x_player.id).unwrap(); + + // game = game.resolve_phase_start(); + + // assert!(game.resolutions.len() == 4); + // while let Some(r) = game.resolutions.last().unwrap().pop() { + // let Resolution { source , target, event: _, stages: _ } = r; + // if [i_construct.id, j_construct.id].contains(&source.id) { + // assert!(target.id == x_construct.id); + // } + // } + // } + + // #[test] + // fn ko_pve_test() { + // let mut game = create_2v2_test_game(); + + // let i_player = game.players[0].clone(); + // let x_player = game.players[1].clone(); + + // let i_construct = i_player.constructs[0].clone(); + // let j_construct = i_player.constructs[1].clone(); + // let x_construct = x_player.constructs[0].clone(); + // let y_construct = x_player.constructs[1].clone(); + + // game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap() + // .add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap() + // .add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).unwrap() + // .add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap() + // .player_ready(i_player.id).unwrap() + // .player_ready(x_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); + + // // kill a construct + // game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(usize::max_value()); + + // assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); + // assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); + + // // add some more skills + // game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, x_construct.id, j_construct.id, Skill::Attack).unwrap(); + // game.add_skill(x_player.id, y_construct.id, j_construct.id, Skill::Attack).unwrap(); + // assert!(game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).is_err()); + + // game.player_ready(i_player.id).unwrap(); + // game.player_ready(x_player.id).unwrap(); + + // assert!(game.skill_phase_finished()); + // game = game.resolve_phase_start(); + + // assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); + // assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); + // return; + // } + + // #[test] + // fn tick_removal_test() { // let mut game = create_test_game(); // let x_player = game.players[0].clone(); @@ -1056,243 +1239,75 @@ mod tests { // let x_construct = x_player.constructs[0].clone(); // let y_construct = y_player.constructs[0].clone(); - // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Absorb); + // // make the purify construct super fast so it beats out decay + // game.construct_by_id(y_construct.id).unwrap().speed.force(10000000); - // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Absorb).is_some() { + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Decay); + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Decay).is_some() { // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); // } + // game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Siphon); + // while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Siphon).is_some() { + // game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); + // } + + // game.construct_by_id(y_construct.id).unwrap().learn_mut(Skill::Purify); + // while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Purify).is_some() { + // game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); + // } + // // apply buff - // game.add_skill(x_player.id, x_construct.id, x_construct.id, Skill::Absorb).unwrap(); + // game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); // game = game.resolve_phase_start(); - // assert!(game.construct_by_id(x_construct.id).unwrap().affected(Effect::Absorb)); + // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Decay)); - // // attack and receive debuff - // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::TestAttack).unwrap(); + // let Resolution { source: _, target: _, event, stages: _ } = game.resolutions.last().unwrap().pop().unwrap(); + // match event { + // Event::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick), + // _ => panic!("not decay"), + // }; + + // game.resolutions.clear(); + + // // remove + // game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); // game.player_ready(x_player.id).unwrap(); // game.player_ready(y_player.id).unwrap(); // game = game.resolve_phase_start(); - // info!("{:#?}", game); - // assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Absorption)); + // while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolutions.last().unwrap().pop() { + // match event { + // Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => + // panic!("{:?} damage event", event), + // _ => (), + // } + // }; + + // game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // game.resolutions.clear(); + + // game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); + // game.player_ready(x_player.id).unwrap(); + // game.player_ready(y_player.id).unwrap(); + // game = game.resolve_phase_start(); + + // while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolutions.last().unwrap().pop() { + // match event { + // Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => + // panic!("{:#?} {:#?} damage event", game.resolutions, event), + // _ => (), + // } + // }; + // } - #[test] - fn aoe_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Ruin); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Ruin).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Ruin).unwrap(); - game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - let ruins = game.resolved - .into_iter() - .filter(|r| { - let Resolution { source, target: _, event, stages: _ } = r; - match source.id == x_construct.id { - true => match event { - Event::Effect { effect, duration, skill: _, construct_effects: _ } => { - assert!(*effect == Effect::Stun); - assert!(*duration == 1); - true - }, - Event::AoeSkill { skill: _ } => false, - Event::Damage { amount: _, mitigation: _, colour: _, skill: _ } => false, - _ => panic!("ruin result not effect {:?}", event), - } - false => false, - } - }) - .count(); - - assert!(ruins == 2); - } - - #[test] - fn intercept_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Intercept); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Intercept).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Intercept).unwrap(); - game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - game = game.resolve_phase_start(); - - assert!(game.resolved.len() == 4); - while let Some(r) = game.resolved.pop() { - let Resolution { source , target, event: _, stages: _ } = r; - if [i_construct.id, j_construct.id].contains(&source.id) { - assert!(target.id == x_construct.id); - } - } - } - - #[test] - fn ko_pve_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap() - .add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap() - .add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).unwrap() - .add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap() - .player_ready(i_player.id).unwrap() - .player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); - - // kill a construct - game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(u64::max_value()); - - assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); - assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); - - // add some more skills - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, j_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, y_construct.id, j_construct.id, Skill::Attack).unwrap(); - assert!(game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).is_err()); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); - assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); - return; - } - - #[test] - fn tick_removal_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - // make the purify construct super fast so it beats out decay - game.construct_by_id(y_construct.id).unwrap().speed.force(10000000); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Decay); - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Decay).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Siphon); - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Siphon).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.construct_by_id(y_construct.id).unwrap().learn_mut(Skill::Purify); - while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Purify).is_some() { - game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); - } - - // apply buff - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Decay)); - - let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - match event { - Event::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick), - _ => panic!("not decay"), - }; - - game.resolved.clear(); - - // remove - game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { - match event { - Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => - panic!("{:?} damage event", event), - _ => (), - } - }; - - game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - game.resolved.clear(); - - game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { - match event { - Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => - panic!("{:#?} {:#?} damage event", game.resolved, event), - _ => (), - } - }; - - } - #[test] fn upkeep_test() { let mut game = create_2v2_test_game(); @@ -1301,4 +1316,17 @@ mod tests { game = game.upkeep(); // assert!(game.players[1].warnings == 1); } + + #[test] + fn attack_test() { + let mut game = create_2v2_test_game(); + let player_id = game.players[0].id; + let source = game.players[0].constructs[0].id; + let target = game.players[1].constructs[0].id; + println!("{:?}", game); + game.add_skill(player_id, source, target, Skill::Attack).unwrap(); + game = game.resolve_phase_start(); + + println!("{:?}", game.resolutions.last().unwrap()); + } } diff --git a/core/src/instance.rs b/core/src/instance.rs index 1923172f..83d7875e 100644 --- a/core/src/instance.rs +++ b/core/src/instance.rs @@ -493,10 +493,10 @@ impl Instance { Ok(self) } - pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result { + pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct: Option) -> Result { self.vbox_action_allowed(account)?; self.account_player(account)? - .vbox_unequip(target, construct_id, target_construct_id)?; + .vbox_unequip(target, construct_id, target_construct)?; Ok(self) } } diff --git a/core/src/item.rs b/core/src/item.rs index 72639073..12121ff3 100644 --- a/core/src/item.rs +++ b/core/src/item.rs @@ -1,7 +1,8 @@ use skill::{Skill}; use spec::{Spec, SpecValues}; use construct::{Colours}; -use effect::{Colour, Cooldown}; +use effect::{Cooldown}; +use game::{Colour}; #[derive(Debug,Copy,Clone,Serialize,Deserialize,PartialEq,PartialOrd,Ord,Eq)] pub enum Item { @@ -337,7 +338,7 @@ impl Item { } } - pub fn base_speed(&self) -> u64 { + pub fn base_speed(&self) -> usize { match self { Item::Attack => 1, Item::Stun => 2, @@ -351,7 +352,7 @@ impl Item { } } - pub fn speed(&self) -> u64 { + pub fn speed(&self) -> usize { match self { Item::Attack | Item::Stun | @@ -1490,7 +1491,7 @@ pub struct ItemInfo { pub spec: bool, pub values: Option, pub skill: bool, - pub speed: Option, + pub speed: Option, pub cooldown: Cooldown, pub description: String, } diff --git a/core/src/player.rs b/core/src/player.rs index c87b8a97..0c1bad1d 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -330,8 +330,8 @@ impl Player { Ok(self) } - pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result<&mut Player, Error> { - if self.vbox.stash.len() >= 9 && !target_construct_id.is_some() { + pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, construct: Option) -> Result<&mut Player, Error> { + if self.vbox.stash.len() >= 9 && !construct.is_some() { return Err(err_msg("too many items stash")); } @@ -363,7 +363,7 @@ impl Player { construct.apply_modifiers(&player_colours); } - match target_construct_id { + match construct { Some(cid) => { self.vbox_apply(target, cid)?; }, None => { self.vbox.stash_add(target, None)?; }, }; diff --git a/core/src/skill.rs b/core/src/skill.rs index b370840b..7c96a778 100644 --- a/core/src/skill.rs +++ b/core/src/skill.rs @@ -2,360 +2,413 @@ 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}; +use game::{Game, Colour, Value, Action}; +use construct::{Construct, ConstructEffect, EffectMeta, Stat}; +use effect::{Effect, Cooldown}; -pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions { - let mut resolutions = vec![]; +// pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) { +// 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 }).stages(EventStages::StartEnd)); +// 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 +// game.event(Resolution::new(&a, &b).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); +// } +// return cast_actions(skill, &mut a, &mut b, resolutions); +// } + +fn modify_cast(game: &Game, cast: Cast) -> Vec { + let target_player = game.players.iter() + .find(|t| t.constructs.iter().any(|c| c.id == cast.target)) + .unwrap(); + + if let Some(t) = target_player.intercepting() { + return vec![Cast { target: t.id, ..cast }]; } - return resolve_skill(skill, &mut a, &mut b, resolutions); + + // if game.construct[source].multistrike() { + // return vec![ + // Cast { target: t.id, ..cast }, + // Cast { target: t.id, ..cast }, + // ]; + // } + + let targets = match cast.skill.aoe() { + true => game.players.iter() + .find(|t| t.constructs.iter().any(|c| c.id == cast.target)) + .unwrap() + .constructs + .iter() + .map(|c| Cast { target: c.id, ..cast }) + .collect(), + false => vec![cast], + }; + + return targets; } -pub fn 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 }).stages(EventStages::StartEnd)); +pub fn resolve(game: &mut Game, cast: Cast) { + let casts = modify_cast(game, cast); + + // let source = game.construct_by_id(cast.source).unwrap().clone(); + // if skill.aoe() { // Send an aoe skill event for anims + // game.event(Resolution::new(&source, + // &game.construct_by_id(cast.target).unwrap().clone()).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); + // } + + for cast in casts { + game.actions(cast_actions(cast)); } - 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_skill(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: Skill, source: &mut Construct, target: &mut Construct, mut resolutions: Vec) -> Resolutions { - if let Some(_disable) = source.disabled(skill) { - // resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly)); - return resolutions; +pub fn cast_actions(cast: Cast) -> Vec { + match cast.skill { + _ => vec![ + Action::Damage { + construct: cast.target, + skill: Skill::Attack, + colour: Colour::Red, + values: vec![Value::Stat { construct: cast.source, stat: Stat::RedPower, mult: Skill::Attack.multiplier() }], + }], } - if target.is_ko() { - // resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }).stages(EventStages::PostOnly)); - return resolutions; - } + // Skill::Strike => game.event( + // Event::Damage { + // colour: Colour::Red, + // amount: SkillPower { construct: cast.source, skill: Skill::Strike } + // }, + // Event::LifeSteal { + // amount: CastDamage { id: cast.id, colour: Colour::Red, target: cast.source }, + // }, + // ), - if target.affected(Effect::Reflect) && skill.colours().contains(&Colour::Blue) && !skill.is_tick() { - // guard against overflow - if source.affected(Effect::Reflect) { - return resolutions; - } - resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill })); - return resolve_skill(skill, &mut source.clone(), source, resolutions); - } + // Skill::Attack => game.event(Event::Damage { + // colour: Colour::Red, + // amount: Stat { construct: cast.source, stat: Stat::RedPower, pct: ATTACK_RED_POWER_PCT } + // }), - if source.affected(Effect::Haste) { - match skill { - Skill::Slay | - Skill::SlayPlus | - Skill::SlayPlusPlus | - Skill::Chaos | - Skill::ChaosPlus | - Skill::ChaosPlusPlus | - Skill::Strike | - Skill::StrikePlus | - Skill::StrikePlusPlus => { - let amount = source.speed().pct(Skill::HasteStrike.multiplier()); - target.deal_red_damage(Skill::HasteStrike, amount) - .into_iter() - .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); - }, - _ => (), - } - } - if source.affected(Effect::Hybrid) { - match skill { - Skill::Blast| - Skill::BlastPlus | - Skill::BlastPlusPlus | - Skill::Chaos | - Skill::ChaosPlus | - Skill::ChaosPlusPlus | - Skill::Siphon | - Skill::SiphonPlus | - Skill::SiphonPlusPlus => { - 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))); - }, - _ => (), - } - } + // Skill::Bash => game.event(Event::Damage { + // colour: Colour::Red, + // amounts: vec![ + // Stat { construct: cast.source, stat: Stat::RedPower, pct: ATTACK_RED_POWER_PCT }, + // Cooldowns { construct: cast.source }, + // ], + // }) + + // // 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).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() { + // return; + // } + + // if let Some(_disable) = source.disabled(skill) { + // game.event(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly)); + // return; + // } + + // if target.is_ko() { + // game.event(Resolution::new(source, target).event(Event::TargetKo { skill }).stages(EventStages::PostOnly)); + // return; + // } + + // if target.affected(Effect::Reflect) && skill.colours().contains(&Colour::Blue) && !skill.is_tick() { + // // guard against overflow + // if source.affected(Effect::Reflect) { + // } + // game.event(Resolution::new(source, target).event(Event::Reflection { skill })); + // return cast_actions(skill, &mut source.clone(), source, resolutions); + // } + + + // // haste_strike_check(game) + + // if source.affected(Effect::Haste) { + // match skill { + // Skill::Slay | + // Skill::SlayPlus | + // Skill::SlayPlusPlus | + // Skill::Chaos | + // Skill::ChaosPlus | + // Skill::ChaosPlusPlus | + // Skill::Strike | + // Skill::StrikePlus | + // Skill::StrikePlusPlus => { + // let amount = source.speed().pct(Skill::HasteStrike.multiplier()); + // target.deal_red_damage(Skill::HasteStrike, amount) + // .into_iter() + // .for_each(|e| game.event(Resolution::new(source, target).event(e))); + // }, + // _ => (), + // } + // } + + // if source.affected(Effect::Hybrid) { + // match skill { + // Skill::Blast| + // Skill::BlastPlus | + // Skill::BlastPlusPlus | + // Skill::Chaos | + // Skill::ChaosPlus | + // Skill::ChaosPlusPlus | + // Skill::Siphon | + // Skill::SiphonPlus | + // Skill::SiphonPlusPlus => { + // let amount = source.green_power().pct(Skill::HybridBlast.multiplier()); + // target.deal_blue_damage(Skill::HybridBlast, amount) + // .into_iter() + // .for_each(|e| game.event(Resolution::new(source, target).event(e))); + // }, + // _ => (), + // } + // } // match self.category() == EffectCategory::Red { // true => { // if let Some(evasion) = target.evade(*self) { - // resolutions.push(evasion); + // game.event(evasion); // return Event; // } // }, // false => (), // } - resolutions = match skill { - Skill::Amplify| - Skill::AmplifyPlus | - Skill::AmplifyPlusPlus => amplify(source, target, resolutions, skill), + // match skill { + // Skill::Amplify| + // Skill::AmplifyPlus | + // Skill::AmplifyPlusPlus => amplify(game, skill), - Skill::Banish| - Skill::BanishPlus | - Skill::BanishPlusPlus => banish(source, target, resolutions, skill), + // Skill::Banish| + // Skill::BanishPlus | + // Skill::BanishPlusPlus => banish(game, skill), - Skill::Bash| - Skill::BashPlus | - Skill::BashPlusPlus => bash(source, target, resolutions, skill), + // Skill::Bash| + // Skill::BashPlus | + // Skill::BashPlusPlus => bash(game, skill), - Skill::Blast| - Skill::BlastPlus | - Skill::BlastPlusPlus => blast(source, target, resolutions, skill), + // Skill::Blast| + // Skill::BlastPlus | + // Skill::BlastPlusPlus => blast(game, skill), - Skill::Chaos| - Skill::ChaosPlus | - Skill::ChaosPlusPlus => chaos(source, target, resolutions, skill), + // Skill::Chaos| + // Skill::ChaosPlus | + // Skill::ChaosPlusPlus => chaos(game, skill), - Skill::Sustain| - Skill::SustainPlus | - Skill::SustainPlusPlus => sustain(source, target, resolutions, skill), + // Skill::Sustain| + // Skill::SustainPlus | + // Skill::SustainPlusPlus => sustain(game, skill), - Skill::Electrify| - Skill::ElectrifyPlus | - Skill::ElectrifyPlusPlus => electrify(source, target, resolutions, skill), - Skill::ElectrocuteTick| - Skill::ElectrocuteTickPlus | - Skill::ElectrocuteTickPlusPlus => electrocute_tick(source, target, resolutions, skill), + // Skill::Electrify| + // Skill::ElectrifyPlus | + // Skill::ElectrifyPlusPlus => electrify(game, skill), + // Skill::ElectrocuteTick| + // Skill::ElectrocuteTickPlus | + // Skill::ElectrocuteTickPlusPlus => electrocute_tick(game, skill), - Skill::Curse| - Skill::CursePlus | - Skill::CursePlusPlus => curse(source, target, resolutions, skill), + // Skill::Curse| + // Skill::CursePlus | + // Skill::CursePlusPlus => curse(game, skill), - Skill::Decay| - Skill::DecayPlus | - Skill::DecayPlusPlus => decay(source, target, resolutions, skill), - Skill::DecayTick| - Skill::DecayTickPlus | - Skill::DecayTickPlusPlus => decay_tick(source, target, resolutions, skill), + // Skill::Decay| + // Skill::DecayPlus | + // Skill::DecayPlusPlus => decay(game, skill), + // Skill::DecayTick| + // Skill::DecayTickPlus | + // Skill::DecayTickPlusPlus => decay_tick(game, skill), - Skill::Haste| - Skill::HastePlus | - Skill::HastePlusPlus => haste(source, target, resolutions, skill), + // Skill::Haste| + // Skill::HastePlus | + // Skill::HastePlusPlus => haste(game, skill), - Skill::Heal| - Skill::HealPlus | - Skill::HealPlusPlus => heal(source, target, resolutions, skill), + // Skill::Heal| + // Skill::HealPlus | + // Skill::HealPlusPlus => heal(game, skill), - Skill::Absorb| - Skill::AbsorbPlus | - Skill::AbsorbPlusPlus => absorb(source, target, resolutions, skill), + // Skill::Absorb| + // Skill::AbsorbPlus | + // Skill::AbsorbPlusPlus => absorb(game, skill), - Skill::Hybrid| - Skill::HybridPlus | - Skill::HybridPlusPlus => hybrid(source, target, resolutions, skill), + // Skill::Hybrid| + // Skill::HybridPlus | + // Skill::HybridPlusPlus => hybrid(game, skill), - Skill::Invert| - Skill::InvertPlus | - Skill::InvertPlusPlus => invert(source, target, resolutions, skill), + // Skill::Invert| + // Skill::InvertPlus | + // Skill::InvertPlusPlus => invert(game, skill), - Skill::Counter| - Skill::CounterPlus | - Skill::CounterPlusPlus => counter(source, target, resolutions, skill), + // Skill::Counter| + // Skill::CounterPlus | + // Skill::CounterPlusPlus => counter(game, skill), - Skill::Purge| - Skill::PurgePlus | - Skill::PurgePlusPlus => purge(source, target, resolutions, skill), + // Skill::Purge| + // Skill::PurgePlus | + // Skill::PurgePlusPlus => purge(game, skill), - Skill::Purify| - Skill::PurifyPlus | - Skill::PurifyPlusPlus => purify(source, target, resolutions, skill), + // Skill::Purify| + // Skill::PurifyPlus | + // Skill::PurifyPlusPlus => purify(game, skill), - Skill::Recharge| - Skill::RechargePlus | - Skill::RechargePlusPlus => recharge(source, target, resolutions, skill), + // Skill::Recharge| + // Skill::RechargePlus | + // Skill::RechargePlusPlus => recharge(game, skill), - Skill::Reflect| - Skill::ReflectPlus | - Skill::ReflectPlusPlus => reflect(source, target, resolutions, skill), + // Skill::Reflect| + // Skill::ReflectPlus | + // Skill::ReflectPlusPlus => reflect(game, skill), - Skill::Ruin| - Skill::RuinPlus | - Skill::RuinPlusPlus => ruin(source, target, resolutions, skill), + // Skill::Ruin| + // Skill::RuinPlus | + // Skill::RuinPlusPlus => ruin(game, skill), - Skill::Link| - Skill::LinkPlus | - Skill::LinkPlusPlus => link(source, target, resolutions, skill), + // Skill::Link| + // Skill::LinkPlus | + // Skill::LinkPlusPlus => link(game, skill), - Skill::Silence| - Skill::SilencePlus | - Skill::SilencePlusPlus => silence(source, target, resolutions, skill), + // Skill::Silence| + // Skill::SilencePlus | + // Skill::SilencePlusPlus => silence(game, skill), - Skill::Siphon| - Skill::SiphonPlus | - Skill::SiphonPlusPlus => siphon(source, target, resolutions, skill), - Skill::SiphonTick| - Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus => siphon_tick(source, target, resolutions, skill), + // Skill::Siphon| + // Skill::SiphonPlus | + // Skill::SiphonPlusPlus => siphon(game, skill), + // Skill::SiphonTick| + // Skill::SiphonTickPlus | + // Skill::SiphonTickPlusPlus => siphon_tick(game, skill), - Skill::Slay| - Skill::SlayPlus | - Skill::SlayPlusPlus => slay(source, target, resolutions, skill), + // Skill::Slay| + // Skill::SlayPlus | + // Skill::SlayPlusPlus => slay(game, skill), - Skill::Sleep| - Skill::SleepPlus | - Skill::SleepPlusPlus => sleep(source, target, resolutions, skill), + // Skill::Sleep| + // Skill::SleepPlus | + // Skill::SleepPlusPlus => sleep(game, skill), - Skill::Restrict| - Skill::RestrictPlus | - Skill::RestrictPlusPlus => restrict(source, target, resolutions, skill), + // Skill::Restrict| + // Skill::RestrictPlus | + // Skill::RestrictPlusPlus => restrict(game, skill), - Skill::Strike| - Skill::StrikePlus | - Skill::StrikePlusPlus => strike(source, target, resolutions, skill), + // Skill::Strike| + // Skill::StrikePlus | + // Skill::StrikePlusPlus => strike(game, skill), - Skill::Intercept| - Skill::InterceptPlus | - Skill::InterceptPlusPlus => intercept(source, target, resolutions, skill), + // Skill::Intercept| + // Skill::InterceptPlus | + // Skill::InterceptPlusPlus => intercept(game, skill), - Skill::Break| - Skill::BreakPlus | - Skill::BreakPlusPlus => break_(source, target, resolutions, skill), + // Skill::Break| + // Skill::BreakPlus | + // Skill::BreakPlusPlus => break_(game, skill), - Skill::Triage| - Skill::TriagePlus | - Skill::TriagePlusPlus => triage(source, target, resolutions, skill), + // Skill::Triage| + // Skill::TriagePlus | + // Skill::TriagePlusPlus => triage(game, skill), - Skill::TriageTick| - Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => triage_tick(source, target, resolutions, skill), + // Skill::TriageTick| + // Skill::TriageTickPlus | + // Skill::TriageTickPlusPlus => triage_tick(game, skill), - // 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), - Skill::Stun => stun(source, target, resolutions, skill), + // // Base Skills + // Skill::Attack => attack(game, skill), + // Skill::Block => block(game, skill), + // Skill::Buff => buff(game, skill), + // Skill::Debuff => debuff(game, skill), + // Skill::Stun => stun(game, skill), - // Triggered - Skill::Electrocute | - Skill::ElectrocutePlus | - Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"), - Skill::HasteStrike => panic!("should only trigger from haste"), - Skill::Absorption| - Skill::AbsorptionPlus | - Skill::AbsorptionPlusPlus => panic!("should only trigger from absorb"), - Skill::HybridBlast => panic!("should only trigger from hybrid"), - Skill::CounterAttack| - Skill::CounterAttackPlus | - Skill::CounterAttackPlusPlus => panic!("should only trigger from counter"), + // // Triggered + // Skill::Electrocute | + // Skill::ElectrocutePlus | + // Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"), + // Skill::HasteStrike => panic!("should only trigger from haste"), + // Skill::Absorption| + // Skill::AbsorptionPlus | + // Skill::AbsorptionPlusPlus => panic!("should only trigger from absorb"), + // Skill::HybridBlast => panic!("should only trigger from hybrid"), + // Skill::CounterAttack| + // Skill::CounterAttackPlus | + // Skill::CounterAttackPlusPlus => panic!("should only trigger from counter"), - // Not used - }; + // // Not used + // }; - return resolutions; -} + // for Resolution { source: event_source, target: event_target, event, stages: _ } in resolutions.clone() { + // let mut source = game.construct_by_id(event_source.id).unwrap().clone(); + // let mut target = game.construct_by_id(event_target.id).unwrap().clone(); -fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { - for Resolution { source: event_source, target: event_target, event, stages: _ } in resolutions.clone() { - let mut source = game.construct_by_id(event_source.id).unwrap().clone(); - let mut target = game.construct_by_id(event_target.id).unwrap().clone(); + // match event { + // Event::Damage { amount, skill, mitigation, colour: c } => { + // if target.affected(Effect::Electric) && !skill.is_tick() { + // let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + // .find(|e| e.effect == Effect::Electric).unwrap().clone(); + // match meta { + // Some(EffectMeta::Skill(s)) => { + // // Gurad against reflect overflow + // if !(source.affected(Effect::Reflect) && target.affected(Effect::Reflect)) { + // // Check reflect don't bother if electrocute is procing on death + // if source.affected(Effect::Reflect) && !target.is_ko() { + // game.event(Resolution::new(&target, &source) + // .event(Event::Reflection { skill: s }).stages(EventStages::EndPost)); + // electrocute(&mut source, &mut target, resolutions, s); + // } else { + // electrocute(&mut target, &mut source, resolutions, s); + // } + // } + // }, + // _ => panic!("no electrify skill"), + // }; + // } - match event { - Event::Damage { amount, skill, mitigation, colour: c } => { - if target.affected(Effect::Electric) && !skill.is_tick() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Electric).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - // Gurad against reflect overflow - if !(source.affected(Effect::Reflect) && target.affected(Effect::Reflect)) { - // Check reflect don't bother if electrocute is procing on death - if source.affected(Effect::Reflect) && !target.is_ko() { - resolutions.push(Resolution::new(&target, &source) - .event(Event::Reflection { skill: s }).stages(EventStages::EndPost)); - resolutions = electrocute(&mut source, &mut target, resolutions, s); - } else { - resolutions = electrocute(&mut target, &mut source, resolutions, s); - } - } - }, - _ => panic!("no electrify skill"), - }; - } + // if target.affected(Effect::Absorb) && !target.is_ko() { + // let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + // .find(|e| e.effect == Effect::Absorb).unwrap().clone(); + // match meta { + // Some(EffectMeta::Skill(s)) => { + // absorption(&mut source, &mut target, resolutions, skill, amount + mitigation, s); + // }, + // _ => panic!("no absorb skill"), + // }; + // } + // if c == Colour::Red { + // if target.affected(Effect::Counter) && !target.is_ko() { + // let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() + // .find(|e| e.effect == Effect::Counter).unwrap().clone(); + // match meta { + // Some(EffectMeta::Skill(s)) => { + // counter_attack(&mut target, &mut source, resolutions, s); + // }, + // _ => panic!("no counter skill"), + // }; + // } + // } - if target.affected(Effect::Absorb) && !target.is_ko() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Absorb).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - resolutions = absorption(&mut source, &mut target, resolutions, skill, amount + mitigation, s); - }, - _ => panic!("no absorb skill"), - }; - } - if c == Colour::Red { - if target.affected(Effect::Counter) && !target.is_ko() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Counter).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - resolutions = counter_attack(&mut target, &mut source, resolutions, s); - }, - _ => panic!("no counter skill"), - }; - } - } - - if target.is_ko() && event_target.green == 0 { - // Make sure target ko is from this event - target.effects.clear(); - resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly)); - } - }, - _ => (), - }; + // if target.is_ko() && event_target.green == 0 { + // // Make sure target ko is from this event + // target.effects.clear(); + // game.event(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly)); + // } + // }, + // _ => (), + // }; - game.update_construct(&mut source); - game.update_construct(&mut target); - }; - - return resolutions; + // game.update_construct(&mut source); + // game.update_construct(&mut target); + // }; } @@ -363,20 +416,20 @@ fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: 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 player: Uuid, + pub source: Uuid, + pub target: Uuid, pub skill: Skill, - pub speed: u64, + pub speed: usize, } impl Cast { - pub fn new(source_construct_id: Uuid, source_player_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Cast { + pub fn new(source: Uuid, player: Uuid, target: Uuid, skill: Skill) -> Cast { return Cast { id: Uuid::new_v4(), - source_construct_id, - source_player_id, - target_construct_id, + source, + player, + target, skill, speed: 0, }; @@ -385,9 +438,9 @@ impl Cast { 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, + source: source.id, + player: source.account, + target: target.id, skill, speed: source.skill_speed(skill), } @@ -401,14 +454,101 @@ impl Cast { pub type Disable = Vec; pub type Immunity = Vec; +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub struct Resolution { + pub target: EventConstruct, + pub variant: EventVariant, + pub stages: EventStages, + pub delay: i64, +} + +impl Resolution { + pub fn new(target: &Construct, variant: EventVariant) -> Resolution { + let stages = variant.stages(); + + Resolution { + target: EventConstruct { + id: target.id, + red: target.red_life(), + green: target.green_life(), + blue: target.blue_life(), + }, + variant, + stages: stages, + delay: stages.delay(), + } + } +} + +#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] +pub enum EventVariant { + Disable { skill: Skill, disable: Disable }, + Immunity { skill: Skill, immunity: Immunity }, + Damage { skill: Skill, amount: usize, mitigation: usize, colour: Colour }, + Healing { skill: Skill, amount: usize, overhealing: usize }, + Recharge { skill: Skill, red: usize, blue: usize }, + Inversion { skill: Skill }, + Reflection { skill: Skill }, + AoeSkill { skill: Skill }, + Skill { skill: Skill }, + Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, + Removal { skill: Skill, effect: Option, construct_effects: Vec }, + TargetKo { skill: Skill }, + // skill not necessary but makes it neater as all events are arrays in js + Ko (), + Forfeit (), + Incomplete (), + // not used + Evasion { skill: Skill, evasion_rating: usize }, +} + +impl EventVariant { + fn stages(&self) -> EventStages { + match self { + EventVariant::Disable { skill, disable} + => EventStages::PostOnly, + EventVariant::Immunity { skill, immunity} + => EventStages::PostOnly, + EventVariant::Damage { skill, amount, mitigation, colour} + => EventStages::PostOnly, + EventVariant::Healing { skill, amount, overhealing} + => EventStages::PostOnly, + EventVariant::Recharge { skill, red, blue} + => EventStages::PostOnly, + EventVariant::Inversion { skill } + => EventStages::PostOnly, + EventVariant::Reflection { skill } + => EventStages::PostOnly, + EventVariant::AoeSkill { skill } + => EventStages::PostOnly, + EventVariant::Skill { skill } + => EventStages::PostOnly, + EventVariant::Effect { skill, effect, duration, construct_effects } + => EventStages::PostOnly, + EventVariant::Removal { skill, effect, construct_effects } + => EventStages::PostOnly, + EventVariant::TargetKo { skill } + => EventStages::PostOnly, + EventVariant::Ko () + => EventStages::PostOnly, + EventVariant::Forfeit () + => EventStages::PostOnly, + EventVariant::Incomplete () + => EventStages::PostOnly, + EventVariant::Evasion { skill, evasion_rating } + => EventStages::PostOnly, + } + } +} + // 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, + pub red: usize, + pub green: usize, + pub blue: usize, } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] @@ -429,52 +569,15 @@ pub enum EventStages { PostOnly, // Skip Skip Anim } -#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] -pub struct Resolution { - pub source: EventConstruct, - pub target: EventConstruct, - pub event: Event, - pub stages: EventStages, -} - -impl Resolution { - fn new(source: &Construct, target: &Construct) -> Resolution { - Resolution { - source: EventConstruct { - id: source.id, - red: source.red_life(), - green: source.green_life(), - blue: source.blue_life(), - }, - target: EventConstruct { - id: target.id, - red: target.red_life(), - green: target.green_life(), - blue: target.blue_life(), - }, - event: Event::Incomplete, - stages: EventStages::AllStages, - } - } - - fn event(mut self, e: Event) -> Resolution { - self.event = e; - self - } - - fn stages(mut self, s: EventStages) -> Resolution { - self.stages = s; - self - } - - pub fn get_delay(self) -> i64 { +impl EventStages { + fn delay(self) -> i64 { let source_duration = 1000; // Time for SOURCE ONLY let target_delay = 500; // Used for Source + Target let target_duration = 1500; // Time for TARGET ONLY let post_skill = 1000; // Time for all POST let source_and_target_total = target_delay + target_duration; // SOURCE + TARGET time - match self.stages { + match self { EventStages::AllStages => source_and_target_total + post_skill, // Anim Anim Anim EventStages::StartEnd => source_and_target_total, // Anim Anim Skip EventStages::StartPost => source_duration + post_skill, // Anim Skip Anim @@ -486,31 +589,6 @@ impl Resolution { } } - -#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] -pub enum Event { - Disable { skill: Skill, disable: Disable }, - Immunity { skill: Skill, immunity: Immunity }, - Damage { skill: Skill, amount: u64, mitigation: u64, colour: Colour }, - Healing { skill: Skill, amount: u64, overhealing: u64 }, - Recharge { skill: Skill, red: u64, blue: u64 }, - Inversion { skill: Skill }, - Reflection { skill: Skill }, - AoeSkill { skill: Skill }, - Skill { skill: Skill }, - Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, - Removal { skill: Skill, effect: Option, construct_effects: Vec }, - TargetKo { skill: Skill }, - // skill not necessary but makes it neater as all events are arrays in js - Ko (), - Forfeit (), - Incomplete, - // not used - Evasion { skill: Skill, evasion_rating: u64 }, -} - -pub type Resolutions = Vec; - #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Skill { Attack, @@ -750,7 +828,7 @@ pub enum Skill { } impl Skill { - pub fn multiplier(&self) -> u64 { + pub fn multiplier(&self) -> usize { match self { // Attack Base Skill::Attack => 80, // Base @@ -1227,7 +1305,7 @@ impl Skill { } } - pub fn speed(&self) -> u64 { + pub fn speed(&self) -> usize { match self { Skill::SiphonTick | Skill::SiphonTickPlus | @@ -1332,855 +1410,815 @@ impl Skill { } } -fn attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn strike(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn stun(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - return results; -} - -fn bash(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - if results.iter().any(|r| match r.event { - Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ } - => effect == Effect::Stun && skill == effect_skill, - _ => false, - }) { - let mut cds = 0; - for cs in target.skills.iter_mut() { - if cs.skill.base_cd().is_some() { - cs.cd = match cs.cd { - None => Some(1), - Some(i) => Some(i + 1), - }; - - cds += 1; - } - } - - let amount = source.red_power().pct(skill.multiplier().pct(100 + 45u64.saturating_mul(cds))); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - return results; -} - - -fn sleep(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn sustain(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let red_amount = source.red_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, 0) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn intercept(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let intercept = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept))); - - let red_amount = source.red_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, 0) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let stun = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, stun))); - let vuln = skill.effect()[1]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly)); - - return results; -} - -fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - 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 { - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0]))); - - return results; -} - -fn counter_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 restrict(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - let s_multi = target.skills - .iter() - .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Red) { - true => acc + 35, - false => acc, - }); - - let amount = source.red_power().pct(skill.multiplier()).pct(s_multi); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - - return results; -} - -fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()) + source.green_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.pct(50)); - for h in heal { - results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); - }; - }, - _ => results.push(Resolution::new(source, target).event(e)), - } - } - - return results; -} - -fn heal(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - return results; -} - -fn triage(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Triage => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - - 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))); - - match skip_tick { - true => return results, - false => return triage_tick(source, target, results, tick_skill) - } -} - -fn triage_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -fn chaos(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let mut rng = thread_rng(); - let b_rng: u64 = rng.gen_range(100, 130); - let amount = source.blue_power().pct(skill.multiplier()).pct(b_rng); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - let r_rng: u64 = rng.gen_range(100, 130); - let amount = source.red_power().pct(skill.multiplier()).pct(r_rng); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - return results; -} - -fn blast(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - return results; -} - -fn amplify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn haste(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn debuff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn decay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - - let wither = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, wither))); - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Decay => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[1]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no decay tick skill"), - }; - let decay = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, decay)) - .stages(EventStages::PostOnly)); - - match skip_tick { - true => return results, - false => return decay_tick(source, target, results, tick_skill) - } -} - -fn decay_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -// electrify is the buff effect -// when attacked it runs electrocute and applies a debuff -fn electrify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let electrify = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, electrify))); - return results;; -} - -fn electrocute(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - // Remove electric buff, no need to display if construct is dead - if !source.is_ko() { - let electric = source.effects.iter().position(|e| e.effect == Effect::Electric); - match electric { - Some(eff) => { - let ce = source.effects.remove(eff); - results.push(Resolution::new(source, source) - .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: source.effects.clone() }) - .stages(EventStages::PostOnly)); - } - None => () - } - } - - 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 skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Electrocute => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, electrocute)) - .stages(EventStages::PostOnly)); - - - match skip_tick { - true => return results, - false => return electrocute_tick(source, target, results, tick_skill) - } -} - -fn electrocute_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -fn ruin(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0])) - .stages(EventStages::PostOnly)); - 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]))); - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, 0, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - return results;; -} - -fn absorption(source: &mut Construct, target: &mut Construct, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions { - let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); - - results.push(Resolution::new(source, target) - .event(target.add_effect(reflect_skill, absorb)) - .stages(EventStages::PostOnly)); - - let absorb_index = target.effects.iter().position(|e| e.effect == Effect::Absorb).expect("No absorb"); - let ce = target.effects.remove(absorb_index); - - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - return results;; -} - -fn curse(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn hybrid(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn invert(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn reflect(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, 0, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results;; -} - -fn recharge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - let red_amount = source.red_power().pct(skill.multiplier()); - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn siphon(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Siphon => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - 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))); - - match skip_tick { - true => return results, - false => 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()) + source.green_power().pct(skill.multiplier()); - let siphon_events = target.deal_blue_damage(skill, amount); - - for e in siphon_events { - match e { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { - results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)); - let heal = source.deal_green_damage(skill, amount); - for h in heal { - results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); - }; - }, - _ => results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)), - } - } - - return results; -} - -fn link(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let amount = source.blue_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn silence(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let s_multi = target.skills - .iter() - .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Blue) { - true => acc + 45, - false => acc, - }); - - let amount = source.blue_power().pct(skill.multiplier()).pct(s_multi); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn purge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - if target.effects.len() > 0 { - target.effects.clear(); - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - } - - let effect = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); - - return results; -} - -fn purify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - if target.effects.len() > 0 { - let amount = source.green_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); - target.effects.clear(); - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - let effect = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); - - return results; -} - -fn banish(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - - let red_damage = target.red_life().pct(skill.multiplier()); - let blue_damage = target.blue_life().pct(skill.multiplier()); - - if red_damage > 0 { - target.deal_red_damage(skill, red_damage) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - if blue_damage > 0 { - target.deal_blue_damage(skill, blue_damage) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly)); - return results; -} - -#[cfg(test)] + +// fn strike(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.red_power().pct(skill.multiplier()); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); + +// } + +// fn stun(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(Resolution::new(source, target).event(target.add_effect(skill, e))))); + +// } + +// fn bash(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(Resolution::new(source, target).event(target.add_effect(skill, e))))); + +// if resolutions.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 + 45usize.saturating_mul(cds))); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// } + + +// fn sleep(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(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| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn sustain(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + +// let red_amount = source.red_power().pct(skill.multiplier()); +// target.recharge(skill, red_amount, 0) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn intercept(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let intercept = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, intercept))); + +// let red_amount = source.red_power().pct(skill.multiplier()); +// target.recharge(skill, red_amount, 0) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn break_(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let stun = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, stun))); +// let vuln = skill.effect()[1]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly)); + +// } + +// fn block(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn buff(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn counter(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, skill.effect()[0]))); + +// } + +// fn counter_attack(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.red_power().pct(skill.multiplier()); +// target.deal_red_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); + +// } + +// fn restrict(source: &mut Construct, target: &mut Construct, skill: Skill) { +// skill.effect().into_iter() +// .for_each(|e| (game.event(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| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + + +// } + +// fn slay(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.red_power().pct(skill.multiplier()) + source.green_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: _ } => { +// game.event(Resolution::new(source, target).event(e)); +// let heal = source.deal_green_damage(skill, amount.pct(50)); +// for h in heal { +// game.event(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); +// }; +// }, +// _ => game.event(Resolution::new(source, target).event(e)), +// } +// } + +// } + +// fn heal(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.green_power().pct(skill.multiplier()); +// target.deal_green_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); +// } + +// fn triage(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Triage => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); + +// 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)); +// game.event(Resolution::new(source, target).event(target.add_effect(skill, triage))); + +// match skip_tick { +// false => return triage_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn triage_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.green_power().pct(skill.multiplier()); +// target.deal_green_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); +// } + +// fn chaos(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let mut rng = thread_rng(); +// let b_rng: usize = 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| game.event(Resolution::new(source, target).event(e))); +// let r_rng: usize = 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| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// fn blast(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e))); +// } + +// fn amplify(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn haste(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn debuff(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn decay(source: &mut Construct, target: &mut Construct, skill: Skill) { + +// let wither = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, wither))); + +// let skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Decay => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); +// 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)); +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, decay)) +// .stages(EventStages::PostOnly)); + +// match skip_tick { +// false => return decay_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn decay_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); +// } + +// // electrify is the buff effect +// // when attacked it runs electrocute and applies a debuff +// fn electrify(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let electrify = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, electrify))); +// } + +// fn electrocute(source: &mut Construct, target: &mut Construct, skill: Skill) { +// // Remove electric buff, no need to display if construct is dead +// if !source.is_ko() { +// let electric = source.effects.iter().position(|e| e.effect == Effect::Electric); +// match electric { +// Some(eff) => { +// let ce = source.effects.remove(eff); +// game.event(Resolution::new(source, source) +// .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: source.effects.clone() }) +// .stages(EventStages::PostOnly)); +// } +// None => () +// } +// } + +// 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 skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Electrocute => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); +// let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, electrocute)) +// .stages(EventStages::PostOnly)); + + +// match skip_tick { +// false => return electrocute_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn electrocute_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); +// } + +// fn ruin(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// game.event(Resolution::new(source, target) +// .event(target.add_effect(skill, skill.effect()[0])) +// .stages(EventStages::PostOnly)); +// } + +// fn absorb(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// let blue_amount = source.blue_power().pct(skill.multiplier()); +// target.recharge(skill, 0, blue_amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// fn absorption(source: &mut Construct, target: &mut Construct, reflect_skill: Skill, amount: usize, skill: Skill) { +// let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); + +// game.event(Resolution::new(source, target) +// .event(target.add_effect(reflect_skill, absorb)) +// .stages(EventStages::PostOnly)); + +// let absorb_index = target.effects.iter().position(|e| e.effect == Effect::Absorb).expect("No absorb"); +// let ce = target.effects.remove(absorb_index); + +// game.event(Resolution::new(source, target) +// .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: target.effects.clone() }) +// .stages(EventStages::PostOnly)); +// } + +// fn curse(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn hybrid(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn invert(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); +// } + +// fn reflect(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + +// let blue_amount = source.blue_power().pct(skill.multiplier()); +// target.recharge(skill, 0, blue_amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn recharge(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); +// let red_amount = source.red_power().pct(skill.multiplier()); +// let blue_amount = source.blue_power().pct(skill.multiplier()); +// target.recharge(skill, red_amount, blue_amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn siphon(source: &mut Construct, target: &mut Construct, skill: Skill) { + +// let skip_tick = target.effects.iter().any(|e| { +// match e.effect { +// Effect::Siphon => source.skill_speed(skill) <= e.tick.unwrap().speed, +// _ => false, +// } +// }); +// 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)); +// game.event(Resolution::new(source, target).event(target.add_effect(skill, siphon))); + +// match skip_tick { +// false => return siphon_tick(source, target, resolutions, tick_skill) +// } +// } + +// fn siphon_tick(source: &mut Construct, target: &mut Construct, skill: Skill) { +// let amount = source.blue_power().pct(skill.multiplier()) + source.green_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: _ } => { +// game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost)); +// let heal = source.deal_green_damage(skill, amount); +// for h in heal { +// game.event(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); +// }; +// }, +// _ => game.event(Resolution::new(source, target).event(e).stages(EventStages::EndPost)), +// } +// } + +// } + +// fn link(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); + +// let amount = source.blue_power().pct(skill.multiplier().saturating_mul(target.effects.len() as usize)); +// target.deal_blue_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn silence(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(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| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); + +// } + +// fn purge(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); +// if target.effects.len() > 0 { +// target.effects.clear(); +// game.event(Resolution::new(source, target) +// .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) +// .stages(EventStages::PostOnly)); +// } + +// let effect = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); + +// } + +// fn purify(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); +// if target.effects.len() > 0 { +// let amount = source.green_power().pct(skill.multiplier().saturating_mul(target.effects.len() as usize)); +// target.effects.clear(); +// game.event(Resolution::new(source, target) +// .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) +// .stages(EventStages::PostOnly)); +// target.deal_green_damage(skill, amount) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } +// let effect = skill.effect()[0]; +// game.event(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); + +// } + +// fn banish(source: &mut Construct, target: &mut Construct, skill: Skill) { +// game.event(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); + +// let red_damage = target.red_life().pct(skill.multiplier()); +// let blue_damage = target.blue_life().pct(skill.multiplier()); + +// if red_damage > 0 { +// target.deal_red_damage(skill, red_damage) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// if blue_damage > 0 { +// target.deal_blue_damage(skill, blue_damage) +// .into_iter() +// .for_each(|e| game.event(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); +// } + +// game.event(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly)); +// } + +// #[cfg(test)] mod tests { use skill::*; #[test] - fn heal_test() { - let mut x = Construct::new() - .named(&"muji".to_string()) - .learn(Skill::Heal); - - let mut y = Construct::new() - .named(&"camel".to_string()) - .learn(Skill::Heal); - - x.deal_red_damage(Skill::Attack, 5); - - heal(&mut y, &mut x, vec![], Skill::Heal); + fn attack_actions_test() { + let cast = Cast::new(Uuid::new_v4(), Uuid::new_v4(), Uuid::new_v4(), Skill::Attack); + let mut actions = cast_actions(cast); + let Action::Damage { construct, skill, values, colour } = actions.remove(0); + assert_eq!(skill, Skill::Attack); + assert_eq!(colour, Colour::Red); } - #[test] - fn decay_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - decay(&mut x, &mut y, vec![], Skill::Decay); - - assert!(y.effects.iter().any(|e| e.effect == Effect::Decay)); - - y.reduce_effect_durations(); - let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay); - // assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount)); - } - - #[test] - fn block_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - // ensure it doesn't have 0 pd - x.red_power.force(100); - y.green_life.force(500); - - block(&mut y.clone(), &mut y, vec![], Skill::Block); - assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); - - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => - assert!(amount < x.red_power().pct(Skill::Attack.multiplier())), - _ => panic!("not damage"), - }; - } - - #[test] - fn sustain_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - x.red_power.force(10000000000000); // multiplication of int max will cause overflow - y.green_life.force(1024); // make tests more flexible if we change stats - - sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain); - assert!(y.affected(Effect::Sustain)); - - let mut results = ruin(&mut x, &mut y, vec![], Skill::Ruin); - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Sustain)), - _ => panic!("not immune cluthc"), - }; - - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - assert!(y.green_life() == 1); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023), - _ => panic!("not damage"), - }; - } - - #[test] - fn invert_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - // give red shield but reduce to 0 - y.red_life.force(64); - y.red_life.reduce(64); - x.red_power.force(512); - invert(&mut y.clone(), &mut y, vec![], Skill::Invert); - assert!(y.affected(Effect::Invert)); - - // heal should deal green damage - heal(&mut x, &mut y, vec![], Skill::Heal); - assert!(y.green_life() < 1024); - - // attack should heal and recharge red shield - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - - // match results.remove(0).event { - // Event::Inversion { skill } => assert_eq!(skill, Skill::Attack), - // _ => panic!("not inversion"), - //}; - - match results.remove(0).event { - Event::Healing { skill: _, overhealing: _, amount } => assert!(amount > 0), - _ => panic!("not healing from inversion"), - }; - - match results.remove(0).event { - Event::Recharge { skill: _, red, blue: _ } => assert!(red > 0), - _ => panic!("not recharge from inversion"), - }; - } - - #[test] - fn reflect_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect); - assert!(y.affected(Effect::Reflect)); - - let mut results = vec![]; - results = resolve_skill(Skill::Blast, &mut x, &mut y, results); - - assert!(x.green_life() < 1024); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Reflection { skill } => assert_eq!(skill, Skill::Blast), - _ => panic!("not reflection"), - }; - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert!(amount > 0), - _ => panic!("not damage"), - }; - } - - #[test] - fn siphon_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - x.blue_power.force(256); - x.green_power.force(220); - x.green_life.force(1024); - y.blue_life.force(0); - x.green_life.reduce(512); - - let mut results = resolve_skill(Skill::Siphon, &mut x, &mut y, vec![]); - - assert!(y.affected(Effect::Siphon)); - assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()))); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Siphon), - _ => panic!("not siphon"), - }; - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) - + 220.pct(Skill::SiphonTick.multiplier())), - _ => panic!("not damage siphon"), - }; - - let Resolution { source: _, target, event, stages: _ } = results.remove(0); - match event { - Event::Healing { amount, skill: _, overhealing: _ } => { - assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier())); - assert_eq!(target.id, x.id); - }, - _ => panic!("not healing"), - }; - } - - #[test] - fn triage_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()); - - // ensure it doesn't have 0 sd - x.blue_power.force(50); - - // remove all mitigation - y.red_life.force(0); - y.blue_life.force(0); - - y.deal_red_damage(Skill::Attack, 5); - let prev_hp = y.green_life(); - - triage(&mut x, &mut y, vec![], Skill::Triage); - - assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); - assert!(y.green_life() > prev_hp); - } - - #[test] - fn recharge_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()); - - y.red_life.force(50); - y.blue_life.force(50); - - y.deal_red_damage(Skill::Attack, 5); - y.deal_blue_damage(Skill::Blast, 5); - - let mut results = recharge(&mut x, &mut y, vec![], Skill::Recharge); - - results.remove(0); - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Recharge { red, blue, skill: _ } => { - assert!(red == 5); - assert!(blue == 5); - } - _ => panic!("result was not recharge"), - } - } - - - #[test] - fn silence_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - silence(&mut x.clone(), &mut x, vec![], Skill::Silence); - assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); - assert!(x.disabled(Skill::Silence).is_some()); - } - - #[test] - fn amplify_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - x.blue_power.force(50); - - amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify); - assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); - assert_eq!(x.blue_power(), 75); - } - - #[test] - fn purify_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - decay(&mut x.clone(), &mut x, vec![], Skill::Decay); - assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); - - purify(&mut x.clone(), &mut x, vec![], Skill::Purify); - assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); - } - - #[test] - fn bash_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()) - .learn(Skill::Stun); - - let stun_cd = y.skills.iter().find(|cs| cs.skill == Skill::Stun).unwrap().cd.unwrap(); - - bash(&mut x, &mut y, vec![], Skill::Bash); - assert!(!x.effects.iter().any(|e| e.effect == Effect::Stun)); - assert!(y.skills.iter().any(|cs| cs.skill == Skill::Stun && cs.cd.unwrap() == stun_cd + 1)); - } - - #[test] - fn purge_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); + // #[test] + // fn heal_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()) + // .learn(Skill::Heal); + + // let mut y = Construct::new() + // .named(&"camel".to_string()) + // .learn(Skill::Heal); - let mut y = Construct::new() - .named(&"pretaliation".to_string()) - .learn(Skill::Heal) - .learn(Skill::HealPlus); - - purge(&mut x, &mut y, vec![], Skill::Purge); - // 2 turns at lvl 1 - assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2)); - assert!(y.disabled(Skill::Heal).is_some()); - } + // x.deal_red_damage(Skill::Attack, 5); + + // heal(&mut y, &mut x, vec![], Skill::Heal); + // } + + // #[test] + // fn decay_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // decay(&mut x, &mut y, vec![], Skill::Decay); + + // assert!(y.effects.iter().any(|e| e.effect == Effect::Decay)); + + // y.reduce_effect_durations(); + // let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay); + // // assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount)); + // } + + // #[test] + // fn block_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // // ensure it doesn't have 0 pd + // x.red_power.force(100); + // y.green_life.force(500); + + // block(&mut y.clone(), &mut y, vec![], Skill::Block); + // assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); + + // attack(&mut x, &mut y, vec![], Skill::Attack); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.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 + // y.green_life.force(1024); // make tests more flexible if we change stats + + // sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain); + // assert!(y.affected(Effect::Sustain)); + + // ruin(&mut x, &mut y, vec![], Skill::Ruin); + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Sustain)), + // _ => panic!("not immune cluthc"), + // }; + + // attack(&mut x, &mut y, vec![], Skill::Attack); + // assert!(y.green_life() == 1); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023), + // _ => panic!("not damage"), + // }; + // } + + // #[test] + // fn invert_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // // give red shield but reduce to 0 + // y.red_life.force(64); + // y.red_life.reduce(64); + // x.red_power.force(512); + // invert(&mut y.clone(), &mut y, vec![], Skill::Invert); + // assert!(y.affected(Effect::Invert)); + + // // heal should deal green damage + // heal(&mut x, &mut y, vec![], Skill::Heal); + // assert!(y.green_life() < 1024); + + // // attack should heal and recharge red shield + // attack(&mut x, &mut y, vec![], Skill::Attack); + + // // match resolutions.remove(0).event { + // // Event::Inversion { skill } => assert_eq!(skill, Skill::Attack), + // // _ => panic!("not inversion"), + // //}; + + // match resolutions.remove(0).event { + // Event::Healing { skill: _, overhealing: _, amount } => assert!(amount > 0), + // _ => panic!("not healing from inversion"), + // }; + + // match resolutions.remove(0).event { + // Event::Recharge { skill: _, red, blue: _ } => assert!(red > 0), + // _ => panic!("not recharge from inversion"), + // }; + // } + + // #[test] + // fn reflect_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"camel".to_string()); + + // reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect); + // assert!(y.affected(Effect::Reflect)); + + // let mut vec![]; + // cast_actions(Skill::Blast, &mut x, &mut y, resolutions); + + // assert!(x.green_life() < 1024); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Reflection { skill } => assert_eq!(skill, Skill::Blast), + // _ => panic!("not reflection"), + // }; + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.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.blue_power.force(256); + // x.green_power.force(220); + // x.green_life.force(1024); + // y.blue_life.force(0); + // x.green_life.reduce(512); + + // cast_actions(Skill::Siphon, &mut x, &mut y, vec![]); + + // assert!(y.affected(Effect::Siphon)); + // assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()))); + + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.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: _ } = resolutions.remove(0); + // match event { + // Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + // + 220.pct(Skill::SiphonTick.multiplier())), + // _ => panic!("not damage siphon"), + // }; + + // let Resolution { source: _, target, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Healing { amount, skill: _, overhealing: _ } => { + // assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier())); + // assert_eq!(target.id, x.id); + // }, + // _ => panic!("not healing"), + // }; + // } + + // #[test] + // fn triage_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()); + + // // ensure it doesn't have 0 sd + // x.blue_power.force(50); + + // // remove all mitigation + // y.red_life.force(0); + // y.blue_life.force(0); + + // y.deal_red_damage(Skill::Attack, 5); + // let prev_hp = y.green_life(); + + // triage(&mut x, &mut y, vec![], Skill::Triage); + + // assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); + // assert!(y.green_life() > prev_hp); + // } + + // #[test] + // fn recharge_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()); + + // y.red_life.force(50); + // y.blue_life.force(50); + + // y.deal_red_damage(Skill::Attack, 5); + // y.deal_blue_damage(Skill::Blast, 5); + + // recharge(&mut x, &mut y, vec![], Skill::Recharge); + + // resolutions.remove(0); + // let Resolution { source: _, target: _, event, stages: _ } = resolutions.remove(0); + // match event { + // Event::Recharge { red, blue, skill: _ } => { + // assert!(red == 5); + // assert!(blue == 5); + // } + // _ => panic!("result was not recharge"), + // } + // } + + + // #[test] + // fn silence_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // silence(&mut x.clone(), &mut x, vec![], Skill::Silence); + // assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); + // assert!(x.disabled(Skill::Silence).is_some()); + // } + + // #[test] + // fn amplify_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // x.blue_power.force(50); + + // amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify); + // assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); + // assert_eq!(x.blue_power(), 75); + // } + + // #[test] + // fn purify_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // decay(&mut x.clone(), &mut x, vec![], Skill::Decay); + // assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); + + // purify(&mut x.clone(), &mut x, vec![], Skill::Purify); + // assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); + // } + + // #[test] + // fn bash_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()) + // .learn(Skill::Stun); + + // let stun_cd = y.skills.iter().find(|cs| cs.skill == Skill::Stun).unwrap().cd.unwrap(); + + // bash(&mut x, &mut y, vec![], Skill::Bash); + // assert!(!x.effects.iter().any(|e| e.effect == Effect::Stun)); + // assert!(y.skills.iter().any(|cs| cs.skill == Skill::Stun && cs.cd.unwrap() == stun_cd + 1)); + // } + + // #[test] + // fn purge_test() { + // let mut x = Construct::new() + // .named(&"muji".to_string()); + + // let mut y = Construct::new() + // .named(&"pretaliation".to_string()) + // .learn(Skill::Heal) + // .learn(Skill::HealPlus); + + // purge(&mut x, &mut y, vec![], Skill::Purge); + // // 2 turns at lvl 1 + // assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2)); + // assert!(y.disabled(Skill::Heal).is_some()); + // } } diff --git a/core/src/spec.rs b/core/src/spec.rs index 736eaba1..cbb8de99 100644 --- a/core/src/spec.rs +++ b/core/src/spec.rs @@ -4,11 +4,11 @@ use util::{IntPct}; #[derive(Debug,Clone,Serialize,Deserialize)] pub struct SpecBonus { pub req: Colours, - pub bonus: u64, + pub bonus: usize, } impl SpecBonus { - pub fn get_bonus(&self, c: &Colours) -> u64 { + pub fn get_bonus(&self, c: &Colours) -> usize { if c.red >= self.req.red && c.blue >= self.req.blue && c.green >= self.req.green { return self.bonus; } @@ -18,16 +18,16 @@ impl SpecBonus { #[derive(Debug,Clone,Serialize,Deserialize)] pub struct SpecValues { - pub base: u64, + pub base: usize, pub bonuses: Vec, } impl SpecValues { - pub fn max_value (&self, c: &Colours) -> u64 { + pub fn max_value (&self, c: &Colours) -> usize { self.bonuses.iter().fold(self.base, |acc, s| acc + s.get_bonus(c)) } - pub fn base (self) -> u64 { + pub fn base (self) -> usize { self.base } } @@ -666,7 +666,7 @@ impl Spec { } } - pub fn apply(&self, modified: u64, base: u64, player_colours: &Colours) -> u64 { + pub fn apply(&self, modified: usize, base: usize, player_colours: &Colours) -> usize { match *self { // Percentage multipliers based on base value Spec::Power | diff --git a/core/src/util.rs b/core/src/util.rs index bf4f3032..f5ab8770 100644 --- a/core/src/util.rs +++ b/core/src/util.rs @@ -17,11 +17,11 @@ // } pub trait IntPct { - fn pct(self, pct: u64) -> u64; + fn pct(self, pct: usize) -> usize; } -impl IntPct for u64 { - fn pct(self, pct: u64) -> u64 { +impl IntPct for usize { + fn pct(self, pct: usize) -> usize { self * pct / 100 } }