use rand::prelude::*; use uuid::Uuid; // timekeeping use chrono::prelude::*; use chrono::Duration; // Db Commons use failure::Error; use failure::err_msg; use construct::{Construct, ConstructEffect, Stat}; use skill::{Skill, Cast}; use effect::{Effect}; use player::{Player}; use instance::{TimeControl}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Phase { Start, Skill, Resolve, Finished, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Colour { Red, Blue, Green, } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Value { Stat { construct: Uuid, stat: Stat, mult: usize }, Fixed { value: usize }, Cooldowns { construct: Uuid, mult: usize }, ColourSkills { construct: Uuid, colour: Colour, mult: usize }, DamageTaken { construct: Uuid, colour: Colour, mult: usize }, // Skills { construct: Uuid, colour: Colour }, } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub enum Action { Hit { construct: Uuid }, Cast { construct: Uuid }, Healing { construct: Uuid, values: Vec, colour: Colour }, Damage { construct: Uuid, values: Vec, colour: Colour }, Effect { construct: Uuid, effect: ConstructEffect }, IncreaseCooldowns { construct: Uuid, turns: usize }, // Recharge { skill: Skill, red: usize, blue: usize }, } #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub struct Event { pub cast: Cast, pub focus: Vec, pub variant: EventVariant, pub delay: i64, } impl Event { pub fn new(cast: Cast, variant: EventVariant) -> Event { let focus = match variant { EventVariant::HitAoe { construct: _ } => vec![cast.source, cast.target], // fixme _ => vec![cast.source, cast.target], }; let delay = variant.delay(); Event { cast, delay, focus, variant, } } } pub type Disable = Vec; pub type Direction = (i8, i8); #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum EventVariant { Cast { construct: Uuid, player: Uuid, direction: Direction }, Hit { construct: Uuid, player: Uuid, direction: Direction }, HitAoe { construct: Uuid }, Damage { construct: Uuid, amount: usize, mitigation: usize, colour: Colour, display: EventConstruct }, Effect { construct: Uuid, effect: Effect, duration: u8, display: EventConstruct }, Removal { construct: Uuid, effect: Option, display: EventConstruct }, Healing { construct: Uuid, amount: usize, overhealing: usize }, Recharge { construct: Uuid, red: usize, blue: usize }, Inversion { construct: Uuid }, Reflection { construct: Uuid }, Ko { construct: Uuid }, CooldownIncrease { construct: Uuid, turns: usize }, CooldownDecrease { construct: Uuid, turns: usize }, Forfeit (), } impl EventVariant { fn delay(&self) -> i64 { // let source_duration = 1000; // Time for SOURCE ONLY let target_duration = 1500; // Time for target animation let target_delay = 500; // Add delay if theres source animation let combat_text_delay = 1300; // Time for all post skill let combat_text_overlap = 600; // overlap between animation and combat text match self { EventVariant::Cast { construct: _, direction: _, player: _ } => target_delay, EventVariant::Hit { construct: _, direction: _, player: _ } | EventVariant::HitAoe { construct: _ } => target_duration - combat_text_overlap, _ => combat_text_delay, } } } // used to show the progress of a construct // while the resolutions are animating #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub struct EventConstruct { pub id: Uuid, pub red: usize, pub green: usize, pub blue: usize, } impl EventConstruct { pub fn new(construct: &Construct) -> EventConstruct { EventConstruct { id: construct.id, red: construct.red_life(), green: construct.green_life(), blue: construct.blue_life(), } } } #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum EventStages { #[serde(rename = "START_SKILL END_SKILL POST_SKILL")] AllStages, // Anim Anim Anim #[serde(rename = "START_SKILL END_SKILL")] StartEnd, // Anim Anim Skip #[serde(rename = "START_SKILL POST_SKILL")] StartPost, // Anim Skip Anim #[serde(rename = "START_SKILL")] StartOnly, // Anim Skip Skip #[serde(rename = "END_SKILL POST_SKILL")] EndPost, // Skip Anim Anim #[serde(rename = "END_SKILL")] EndOnly, // Skip Anim Skip #[serde(rename = "POST_SKILL")] PostOnly, // Skip Skip Anim } 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 { 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 EventStages::StartOnly => source_duration, // Anim Skip Skip EventStages::EndPost => target_duration + post_skill, // Skip Anim Anim EventStages::EndOnly => target_duration, // Skip Anim Skip EventStages::PostOnly => post_skill, // Skip Skip Anim } } } #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Game { pub id: Uuid, pub player_constructs: usize, pub player_num: usize, pub players: Vec, pub phase: Phase, pub stack: Vec, pub events: Vec>, pub instance: Option, pub time_control: TimeControl, pub phase_start: DateTime, pub phase_end: Option>, } impl Game { pub fn new() -> Game { return Game { id: Uuid::new_v4(), player_constructs: 0, player_num: 0, players: vec![], phase: Phase::Start, stack: vec![], events: vec![], instance: None, time_control: TimeControl::Standard, phase_end: None, phase_start: Utc::now(), }; } pub fn redact(mut self, account: Uuid) -> Game { self.players = self.players.into_iter() .map(|p| p.redact(account)) .collect(); self.stack .retain(|s| s.player == account); self } pub fn set_time_control(&mut self, tc: TimeControl) -> &mut Game { self.time_control = tc; self.phase_end = Some(tc.lobby_timeout()); self } pub fn set_player_num(&mut self, size: usize) -> &mut Game { self.player_num = size; self } pub fn set_player_constructs(&mut self, size: usize) -> &mut Game { self.player_constructs = size; self } pub fn set_instance(&mut self, id: Uuid) -> &mut Game { self.instance = Some(id); self } pub fn player_add(&mut self, mut player: Player) -> Result<&mut Game, Error> { if self.players.len() == self.player_num { return Err(err_msg("maximum number of players")); } if self.players.iter().any(|t| t.id == player.id) { return Err(err_msg("player already in game")); } if player.constructs.iter().all(|c| c.skills.len() == 0) { info!("WARNING: {:?} has no skills and has forfeited {:?}", player.name, self.id); // self.log.push(format!("{:} has forfeited the game", player.name)); player.forfeit(); } // let player_description = player.constructs.iter().map(|c| c.name.clone()).collect::>().join(", "); // self.log.push(format!("{:} has joined the game. [{:}]", player.name, player_description)); player.constructs.sort_unstable_by_key(|c| c.id); self.players.push(player); Ok(self) } // handle missing player properly pub fn player_by_id(&mut self, id: Uuid) -> Result<&mut Player, Error> { self.players .iter_mut() .find(|t| t.id == id) .ok_or(format_err!("{:?} not in game", id)) } pub fn construct_by_id(&mut self, id: Uuid) -> Option<&mut Construct> { match self.players.iter_mut().find(|t| t.constructs.iter().any(|c| c.id == id)) { Some(player) => player.constructs.iter_mut().find(|c| c.id == id), None => None, } } fn all_constructs(&self) -> Vec { self.players.clone() .into_iter() .flat_map( |t| t.constructs .into_iter()) .collect::>() } pub fn update_construct(&mut self, construct: &mut Construct) -> &mut Game { match self.players.iter_mut().find(|t| t.constructs.iter().any(|c| c.id == construct.id)) { Some(player) => { let index = player.constructs.iter().position(|t| t.id == construct.id).unwrap(); player.constructs.remove(index); player.constructs.push(construct.clone()); player.constructs.sort_unstable_by_key(|c| c.id); }, None => panic!("construct not in game"), }; self } pub fn can_start(&self) -> bool { return self.players.len() == self.player_num && self.players.iter().all(|t| t.constructs.len() == self.player_constructs) } pub fn start(self) -> Game { // self.log.push("Game starting...".to_string()); // both forfeit ddue to no skills if self.finished() { return self.finish(); } self.skill_phase_start(0) } fn skill_phase_start(mut self, resolution_animation_ms: i64) -> Game { self.events.push(vec![]); self.phase_start = Utc::now() .checked_add_signed(Duration::milliseconds(resolution_animation_ms)) .expect("could not set phase start"); self.phase_end = self.time_control.game_phase_end(resolution_animation_ms); for player in self.players.iter_mut() { if player.skills_required() == 0 { continue; } player.set_ready(false); for construct in player.constructs.iter_mut() { for i in 0..construct.skills.len() { if let Some(_d) = construct.disabled(construct.skills[i].skill) { // info!("{:?} disabled {:?}", construct.skills[i].skill, d); construct.skills[i].disabled = true; } else { construct.skills[i].disabled = false; } } } } // self.log.push("".to_string()); if ![Phase::Start, Phase::Resolve].contains(&self.phase) { panic!("game not in Resolve or start phase"); } self.phase = Phase::Skill; self.pve_add_skills(); if self.skill_phase_finished() { return self.resolve_phase_start() } self } fn pve_add_skills(&mut self) -> &mut Game { let mut pve_skills = vec![]; let mut rng = thread_rng(); for mobs in self.players .iter() .filter(|t| t.bot) { let player_player = self.players.iter().find(|t| t.id != mobs.id).unwrap(); for mob in mobs.constructs.iter() { let skill = mob.mob_select_skill(); // info!("{:?} {:?}", mob.name, skill); match skill { Some(s) => { // the mut marks it as being able to be called // more than once let mut find_target = || { match s.defensive() { true => &mobs.constructs[rng.gen_range(0, mobs.constructs.len())], false => &player_player.constructs[rng.gen_range(0, player_player.constructs.len())], } }; let mut target = find_target(); while target.is_ko() { target = find_target(); } pve_skills.push((mobs.id, mob.id, target.id, s)); }, None => continue, }; } } for (player_id, mob_id, target_id, s) in pve_skills { match self.add_skill(player_id, mob_id, target_id, s) { Ok(_) => (), Err(e) => { info!("{:?}", self.construct_by_id(mob_id)); panic!("{:?} unable to add pve mob skill {:?}", e, s); }, } self.player_ready(player_id).unwrap(); } self } pub 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)?; if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } // target checks { let target = match self.construct_by_id(target) { Some(c) => c, None => return Err(err_msg("target construct not in game")), }; // fixme for rez if target.is_ko() { return Err(err_msg("target construct is ko")); } } // construct checks { let construct = match self.construct_by_id(source) { Some(c) => c, None => return Err(err_msg("construct not in game")), }; if construct.is_ko() { return Err(err_msg("construct is ko")); } // check the construct has the skill if !construct.knows(skill) { return Err(err_msg("construct does not have that skill")); } if construct.skill_on_cd(skill).is_some() { return Err(err_msg("abiltity on cooldown")); } // check here as well so uncastable spells don't go on the stack if let Some(disable) = construct.disabled(skill) { return Err(format_err!("skill disabled {:?}", disable)); } } // replace construct skill if let Some(s) = self.stack.iter_mut().position(|s| s.source == source) { self.stack.remove(s); } let skill = Cast::new(source, player_id, target, skill); self.stack.push(skill); return Ok(self); } pub fn offer_draw(mut self, player_id: Uuid) -> Result { if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } { let player = self.player_by_id(player_id)?; player.draw_offered = true; } // bots automatically accept draws for player in self.players.iter_mut() { if player.bot { player.draw_offered = true; } } if self.players.iter().all(|p| p.draw_offered) { return Ok(self.finish()); } return Ok(self); } pub fn concede(mut self, player_id: Uuid) -> Result { if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } self.player_by_id(player_id)? .forfeit(); return Ok(self.finish()); } pub fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { self.player_by_id(player_id)?; if self.phase != Phase::Skill { 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).unwrap().account != player_id); return Ok(self); } pub fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } self.player_by_id(player_id)? .set_ready(true); Ok(self) } pub fn skill_phase_finished(&self) -> bool { self.players.iter().all(|t| t.ready) // self.players.iter() // // for every player // .all(|t| self.stack.iter() // // the number of skills they have cast // .filter(|s| s.player == t.id).collect::>() // // should equal the number required this turn // .len() == t.skills_required() // ) } pub fn resolve_phase_start(mut self) -> Game { if self.phase != Phase::Skill { panic!("game not in skill phase"); } self.phase = Phase::Resolve; // self.log.push("".to_string()); self.resolve_stack() } fn stack_sort_speed(&mut self) -> &mut Game { let mut sorted = self.stack.clone(); sorted.iter_mut() .for_each(|s| { if !s.skill.is_tick() { let caster = self.construct_by_id(s.source).unwrap(); let speed = caster.skill_speed(s.skill); s.speed = speed; } }); sorted.sort_unstable_by_key(|s| s.speed); self.stack = sorted; self } fn finalise_cast(&self, cast: Cast) -> Vec { let target_player = self.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 }]; } // if self.construct[source].multistrike() { // return vec![ // Cast { target: t.id, ..cast }, // Cast { target: t.id, ..cast }, // ]; // } let targets = match cast.skill.aoe() { true => self.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; } fn resolve_stack(mut self) -> Game { if self.phase != Phase::Resolve { panic!("game not in Resolve phase"); } // find their statuses with ticks let mut ticks = self.all_constructs() .iter() .flat_map( |c| c.effects .iter() .cloned() .filter_map(|e| e.tick)) .collect::>(); // add them to the stack self.stack.append(&mut ticks); self.stack_sort_speed(); // temp vec of this round's resolving skills // because need to check cooldown use before pushing them into the complete list let mut casters = vec![]; let mut r_animation_ms = 0; while let Some(cast) = self.stack.pop() { // info!("{:} casts ", cast); let casts = self.finalise_cast(cast); for cast in casts { self.resolve(cast); } // r_animation_ms = events.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 events.len() > 0 && cast.used_cooldown() { // casters.push(cast); // } // sort the stack again in case speeds have changed self.stack_sort_speed(); }; // info!("{:#?}", self.casts); // handle cooldowns and statuses self.progress_durations(&casters); if self.finished() { return self.finish() } self.skill_phase_start(r_animation_ms) } pub fn resolve(&mut self, cast: Cast) -> &mut Game { let mut events_group = vec![]; let skill = cast.skill; // calculate values first? // for result damage value need to pass &events and .find() for action in cast.actions() { let mut events = match action { Action::Cast { construct } => self.cast(cast), Action::Hit { construct } => self.hit(construct, skill), Action::Damage { construct, values, colour } => self.damage(construct, values, colour), Action::Healing { construct, values, colour } => unimplemented!(), Action::Effect { construct, effect } => self.effect(construct, effect), Action::IncreaseCooldowns { construct, turns } => self.increase_cooldowns(construct, turns), }; events_group.append(&mut events); } self.events.last_mut().unwrap().append(&mut events_group); self } fn cast(&mut self, cast: Cast) -> Vec { vec![Event::new(cast, EventVariant::Cast { construct: cast.source, player: cast.player, direction: self.direction(cast) })] } fn hit(&mut self, construct: Uuid, skill: Skill) -> Vec { vec![Event::new(EventVariant::Hit { skill: skill }, construct)] } fn damage(&mut self, construct: Uuid, values: Vec, colour: Colour) -> Vec { match colour { _ => self.construct_by_id(construct).unwrap().deal_red_damage(128) // fixme unwrap } } fn effect(&mut self, construct: Uuid, effect: ConstructEffect) -> Vec { self.construct_by_id(construct).unwrap().add_effect(effect) } fn increase_cooldowns(&mut self, construct: Uuid, turns: usize) -> Vec { self.construct_by_id(construct).unwrap().increase_cooldowns(turns) } fn direction(&mut self, cast: Cast) -> (i8, i8) { let i = self.players.iter() .find(|t| t.constructs.iter().any(|c| c.id == cast.source)) .unwrap().constructs .iter() .position(|c| c.id == cast.source) .unwrap() as i8; let j = self.players.iter() .find(|t| t.constructs.iter().any(|c| c.id == cast.target)) .unwrap().constructs .iter() .position(|c| c.id == cast.target) .unwrap() as i8; let x = j - i; let target = self.construct_by_id(cast.target).unwrap(); // is the caster player account same as target player account side of screen let y = match cast.player == target.account { true => 0, false => 1 }; (x, y) } fn progress_durations(&mut self, events: &Vec) -> &mut Game { for mut construct in self.all_constructs() { // info!("progressing durations for {:}", construct.name); if construct.is_ko() { continue; } match events.iter().find(|s| s.source == construct.id) { Some(skill) => { construct.skill_set_cd(skill.skill); }, None => { construct.reduce_cooldowns(); }, }; // always reduce durations construct.reduce_effect_durations(); self.update_construct(&mut construct); } self } // fn log_resolution(&mut self, speed: usize, resolution: &Event) -> &mut Game { // let Event { source, target, event, stages: _ } = resolution; // match event { // Event::Ko { skill: _ }=> // self.log.push(format!("{:} KO!", target.name)), // Event::Disable { skill, disable } => // self.log.push(format!("{:} {:?} {:} disabled {:?}", // source.name, skill, target.name, disable)), // Event::Immunity { skill, immunity } => // self.log.push(format!("[{:}] {:} {:?} {:} immune {:?}", // speed, source.name, skill, target.name, immunity)), // Event::TargetKo { skill } => // self.log.push(format!("[{:}] {:} {:?} {:} - target is KO", // speed, source.name, skill, target.name)), // Event::Damage { skill, amount, mitigation, colour: _ } => // self.log.push(format!("[{:}] {:} {:?} {:} {:} ({:} mitigated)", // speed, source.name, skill, target.name, amount, mitigation)), // Event::Healing { skill, amount, overhealing } => // self.log.push(format!("[{:}] {:} {:?} {:} {:} healing ({:}OH)", // speed, source.name, skill, target.name, amount, overhealing)), // Event::Inversion { skill } => // self.log.push(format!("[{:}] {:} {:?} {:} INVERTED", // speed, source.name, skill, target.name)), // Event::Reflection { skill } => // self.log.push(format!("[{:}] {:} {:?} {:} REFLECTED", // speed, source.name, skill, target.name)), // Event::Effect { skill, effect, duration, construct_effects: _ } => // self.log.push(format!("[{:}] {:} {:?} {:} {:?} {:}T", // speed, source.name, skill, target.name, effect, duration)), // Event::Skill { skill } => // self.log.push(format!("[{:}] {:} {:?} {:}", // speed, source.name, skill, target.name)), // Event::Removal { effect, construct_effects: _ } => // self.log.push(format!("[{:}] {:?} removed {:} {:?}", // speed, source.name, target.name, effect)), // Event::Recharge { skill, red, blue } => // self.log.push(format!("[{:}] {:} {:?} {:} {:}R {:}B", // speed, source.name, skill, target.name, red, blue)), // Event::Evasion { skill, evasion_rating } => // self.log.push(format!("[{:}] {:} {:?} {:} evaded ({:}%)", // speed, source.name, skill, target.name, evasion_rating)), // Event::Incomplete => panic!("incomplete resolution {:?}", resolution), // } // self // } pub fn finished(&self) -> bool { self.phase == Phase::Finished || self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) } pub fn winner(&self) -> Option<&Player> { match self.players.iter().any(|t| t.constructs.iter().all(|c| c.is_ko())) { true => self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko())), false => None, } } fn finish(mut self) -> Game { self.phase = Phase::Finished; // self.log.push(format!("Game finished.")); // { // let winner = self.players.iter().find(|t| t.constructs.iter().any(|c| !c.is_ko())); // match winner { // Some(w) => self.log.push(format!("Winner: {:}", w.name)), // None => self.log.push(format!("Game was drawn.")), // }; // } self } fn phase_timed_out(&self) -> bool { match self.phase_end { Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0, None => false, } } pub fn upkeep(mut self) -> Game { if self.phase == Phase::Finished { return self; } if !self.phase_timed_out() { return self; } info!("upkeep game: {:} vs {:}", self.players[0].name, self.players[1].name); for player in self.players.iter_mut() { if !player.ready { player.set_ready(true); // player.add_warning(); // info!("upkeep: {:} warned", player.name); // if player.warnings >= 3 { // player.forfeit(); // info!("upkeep: {:} forfeited", player.name); // //todo // // self.events.push(forfeit) // // self.log.push(format!("{:} forfeited.", player.name)); // } } } self = self.resolve_phase_start(); self } } #[cfg(test)] mod tests { use game::*; use construct::*; use util::IntPct; fn create_test_game() -> Game { let mut x = Construct::new() .named(&"pronounced \"creeep\"".to_string()) .learn(Skill::Attack) .learn(Skill::Stun) .learn(Skill::Attack) .learn(Skill::Block) .learn(Skill::Counter) .learn(Skill::Siphon) .learn(Skill::Amplify) .learn(Skill::Stun) .learn(Skill::Block) .learn(Skill::Sleep) .learn(Skill::Decay); let mut y = Construct::new() .named(&"lemongrass tea".to_string()) .learn(Skill::Attack) .learn(Skill::Stun) .learn(Skill::Attack) .learn(Skill::Block) .learn(Skill::Counter) .learn(Skill::Siphon) .learn(Skill::Amplify) .learn(Skill::Stun) .learn(Skill::Block); let mut game = Game::new(); game .set_player_num(2) .set_player_constructs(1); let x_player_id = Uuid::new_v4(); x.account = x_player_id; let x_player = Player::new(x_player_id, None, &"ntr".to_string(), vec![x]); let y_player_id = Uuid::new_v4(); y.account = y_player_id; let y_player = Player::new(y_player_id, None, &"mash".to_string(), vec![y]); game .player_add(x_player).unwrap() .player_add(y_player).unwrap(); assert!(game.can_start()); return game.start(); } fn create_2v2_test_game() -> Game { let mut i = Construct::new() .named(&"pretaliate".to_string()) .learn(Skill::Attack) .learn(Skill::Attack); let mut j = Construct::new() .named(&"poy sian".to_string()) .learn(Skill::Attack) .learn(Skill::Attack); let mut x = Construct::new() .named(&"pronounced \"creeep\"".to_string()) .learn(Skill::Attack) .learn(Skill::Attack); let mut y = Construct::new() .named(&"lemongrass tea".to_string()) .learn(Skill::Attack) .learn(Skill::Attack); let mut game = Game::new(); game .set_player_num(2) .set_player_constructs(2); let i_player_id = Uuid::new_v4(); i.account = i_player_id; j.account = i_player_id; let i_player = Player::new(i_player_id, None, &"ntr".to_string(), vec![i, j]); let x_player_id = Uuid::new_v4(); x.account = x_player_id; y.account = x_player_id; let x_player = Player::new(x_player_id, None, &"mashy".to_string(), vec![x, y]); game .player_add(i_player).unwrap() .player_add(x_player).unwrap(); assert!(game.can_start()); 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() { // // 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 Event { source: _, target: _, event, stages: _ } = game.events.last.unwrap().pop().unwrap(); // // match event { // // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link), // // _ => panic!("not siphon"), // // }; // // let Event { source: _, target: _, event, stages: _ } = game.events.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 Event { source: _, target, event, stages: _ } = game.events.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.events // .last().unwrap() // .into_iter() // .filter(|r| { // let Event { 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.events.len() == 4); // while let Some(r) = game.events.last().unwrap().pop() { // let Event { 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(); // 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 Event { source: _, target: _, event, stages: _ } = game.events.last().unwrap().pop().unwrap(); // match event { // Event::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick), // _ => panic!("not decay"), // }; // game.events.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(Event { source: _, target: _, event, stages: _ }) = game.events.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.events.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(Event { source: _, target: _, event, stages: _ }) = game.events.last().unwrap().pop() { // match event { // Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => // panic!("{:#?} {:#?} damage event", game.events, event), // _ => (), // } // }; // } #[test] fn upkeep_test() { let mut game = create_2v2_test_game(); game.players[0].set_ready(true); game.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap()); 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; game.add_skill(player_id, source, target, Skill::Attack).unwrap(); game = game.resolve_phase_start(); println!("{:?}", game); } #[test] fn bash_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; game.resolve(Cast::new(source, player_id, target, Skill::Bash)); println!("{:?}", game.events); } }