1560 lines
56 KiB
Rust
1560 lines
56 KiB
Rust
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<Value>, colour: Colour },
|
|
Damage { construct: Uuid, values: Vec<Value>, 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<Uuid>,
|
|
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<Effect>;
|
|
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<Effect>, 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<Player>,
|
|
pub phase: Phase,
|
|
pub stack: Vec<Cast>,
|
|
pub events: Vec<Vec<Event>>,
|
|
pub instance: Option<Uuid>,
|
|
pub time_control: TimeControl,
|
|
pub phase_start: DateTime<Utc>,
|
|
pub phase_end: Option<DateTime<Utc>>,
|
|
}
|
|
|
|
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::<Vec<String>>().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<Construct> {
|
|
self.players.clone()
|
|
.into_iter()
|
|
.flat_map(
|
|
|t| t.constructs
|
|
.into_iter())
|
|
.collect::<Vec<Construct>>()
|
|
}
|
|
|
|
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("<Skill Phase>".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<Game, Error> {
|
|
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<Game, Error> {
|
|
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::<Vec<&Cast>>()
|
|
// // 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("<Resolve Phase>".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<Cast> {
|
|
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::<Vec<Cast>>();
|
|
|
|
// 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<Event> {
|
|
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<Event> {
|
|
vec![Event::new(EventVariant::Hit { skill: skill }, construct)]
|
|
}
|
|
|
|
fn damage(&mut self, construct: Uuid, values: Vec<Value>, colour: Colour) -> Vec<Event> {
|
|
match colour {
|
|
_ => self.construct_by_id(construct).unwrap().deal_red_damage(128) // fixme unwrap
|
|
}
|
|
}
|
|
|
|
fn effect(&mut self, construct: Uuid, effect: ConstructEffect) -> Vec<Event> {
|
|
self.construct_by_id(construct).unwrap().add_effect(effect)
|
|
}
|
|
|
|
fn increase_cooldowns(&mut self, construct: Uuid, turns: usize) -> Vec<Event> {
|
|
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<Cast>) -> &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);
|
|
}
|
|
}
|