diff --git a/core/fixme.md b/core/fixme.md index d05c4692..1370b752 100644 --- a/core/fixme.md +++ b/core/fixme.md @@ -1,6 +1,8 @@ # FIXME +game ready not auto starting resolve phase remove immunity +banish dmg aoe event cooldown events leak skills hit event diff --git a/core/src/game.rs b/core/src/game.rs index b1947ccb..8a6dd536 100644 --- a/core/src/game.rs +++ b/core/src/game.rs @@ -57,9 +57,9 @@ pub struct Game { pub stack: Vec, pub events: Vec>, pub instance: Option, - time_control: TimeControl, - phase_start: DateTime, - phase_end: Option>, + pub time_control: TimeControl, + pub phase_start: DateTime, + pub phase_end: Option>, } impl Game { @@ -285,7 +285,7 @@ impl Game { self } - fn add_skill(&mut self, player_id: Uuid, source: Uuid, target: Uuid, skill: Skill) -> Result<&mut Game, Error> { + 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)?; @@ -343,7 +343,7 @@ impl Game { return Ok(self); } - fn offer_draw(mut self, player_id: Uuid) -> Result { + pub fn offer_draw(mut self, player_id: Uuid) -> Result { if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } @@ -367,7 +367,7 @@ impl Game { return Ok(self); } - fn concede(mut self, player_id: Uuid) -> Result { + pub fn concede(mut self, player_id: Uuid) -> Result { if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } @@ -379,7 +379,7 @@ impl Game { } - fn clear_skill(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { + 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")); @@ -390,7 +390,7 @@ impl Game { return Ok(self); } - fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Game, Error> { + 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")); } @@ -401,7 +401,7 @@ impl Game { Ok(self) } - fn skill_phase_finished(&self) -> bool { + pub fn skill_phase_finished(&self) -> bool { self.players.iter().all(|t| t.ready) // self.players.iter() // // for every player @@ -413,7 +413,7 @@ impl Game { // ) } - fn resolve_phase_start(mut self) -> Game { + pub fn resolve_phase_start(mut self) -> Game { if self.phase != Phase::Skill { panic!("game not in skill phase"); } @@ -722,11 +722,11 @@ mod tests { let x_player_id = Uuid::new_v4(); x.account = x_player_id; - let x_player = Player::new(x_player_id, &"ntr".to_string(), vec![x]); + 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, &"mash".to_string(), vec![y]); + let y_player = Player::new(y_player_id, None, &"mash".to_string(), vec![y]); game .player_add(x_player).unwrap() @@ -767,12 +767,12 @@ mod tests { 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, &"ntr".to_string(), vec![i, j]); + 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, &"mashy".to_string(), vec![x, y]); + let x_player = Player::new(x_player_id, None, &"mashy".to_string(), vec![x, y]); game .player_add(i_player).unwrap() diff --git a/core/src/instance.rs b/core/src/instance.rs index 83d7875e..27d09bc0 100644 --- a/core/src/instance.rs +++ b/core/src/instance.rs @@ -99,14 +99,14 @@ pub struct Instance { time_control: TimeControl, phase: InstancePhase, - phase_end: Option>, - phase_start: DateTime, + pub phase_end: Option>, + pub phase_start: DateTime, winner: Option, } impl Instance { - fn new() -> Instance { + pub fn new() -> Instance { Instance { id: Uuid::new_v4(), players: vec![], @@ -170,7 +170,7 @@ impl Instance { (self, new_game) } - fn set_name(mut self, name: String) -> Result { + pub fn set_name(mut self, name: String) -> Result { if name.len() == 0 { return Err(err_msg("name must have a length")); } @@ -179,12 +179,12 @@ impl Instance { Ok(self) } - fn set_time_control(mut self, tc: TimeControl) -> Instance { + pub fn set_time_control(mut self, tc: TimeControl) -> Instance { self.time_control = tc; self } - fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { + pub fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { if self.players.len() >= self.max_players { return Err(err_msg("game full")) } @@ -198,7 +198,7 @@ impl Instance { Ok(self) } - fn player_ready(&mut self, player_id: Uuid) -> Result, Error> { + pub fn player_ready(&mut self, player_id: Uuid) -> Result, Error> { if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) { return Err(err_msg("instance not in start or vbox phase")); } @@ -300,7 +300,7 @@ impl Instance { self.next_round() } - fn next_round(&mut self) -> &mut Instance { + pub fn next_round(&mut self) -> &mut Instance { if self.finish_condition() { return self.finish(); } @@ -342,7 +342,7 @@ impl Instance { self } - fn finished(&self) -> bool { + pub fn finished(&self) -> bool { self.phase == InstancePhase::Finished } @@ -370,7 +370,7 @@ impl Instance { self } - fn current_game_id(&self) -> Option { + pub fn current_game_id(&self) -> Option { if self.phase != InstancePhase::InProgress { return None; } @@ -386,7 +386,7 @@ impl Instance { return current_round.game_id; } - fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { + pub fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { { let current_round = self.rounds .iter_mut() @@ -428,14 +428,14 @@ impl Instance { } // PLAYER ACTIONS - fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> { + pub fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> { self.players .iter_mut() .find(|p| p.id == account) .ok_or(err_msg("account not in instance")) } - fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> { + pub fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> { self.players .iter_mut() .find(|p| p.id != account) @@ -529,7 +529,7 @@ mod tests { let _instance = Instance::new(); let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); + let _player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true); } #[test] @@ -540,7 +540,7 @@ mod tests { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"a".to_string(), constructs); + let player = Player::new(player_account, None, &"a".to_string(), constructs); let a_id = player.id; instance.add_player(player).expect("could not add player"); @@ -548,7 +548,7 @@ mod tests { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"b".to_string(), constructs); + let player = Player::new(player_account, None, &"b".to_string(), constructs); let b_id = player.id; instance.add_player(player).expect("could not add player"); @@ -577,7 +577,7 @@ mod tests { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"a".to_string(), constructs); + let player = Player::new(player_account, None, &"a".to_string(), constructs); let a_id = player.id; instance.add_player(player).expect("could not add player"); @@ -585,7 +585,7 @@ mod tests { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"b".to_string(), constructs); + let player = Player::new(player_account, None, &"b".to_string(), constructs); let b_id = player.id; instance.add_player(player).expect("could not add player"); @@ -617,7 +617,7 @@ mod tests { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"a".to_string(), constructs); + let player = Player::new(player_account, None, &"a".to_string(), constructs); let _a_id = player.id; instance.add_player(player).expect("could not add player"); diff --git a/core/src/mob.rs b/core/src/mob.rs index 2cab954b..2dc40a27 100644 --- a/core/src/mob.rs +++ b/core/src/mob.rs @@ -25,6 +25,6 @@ pub fn instance_mobs(player_id: Uuid) -> Vec { pub fn bot_player() -> Player { let bot_id = Uuid::new_v4(); let constructs = instance_mobs(bot_id); - Player::new(bot_id, &name(), constructs).set_bot(true) + Player::new(bot_id, None, &name(), constructs).set_bot(true) } diff --git a/core/src/player.rs b/core/src/player.rs index 0c1bad1d..cfab3bb9 100644 --- a/core/src/player.rs +++ b/core/src/player.rs @@ -66,10 +66,10 @@ pub struct Player { } impl Player { - pub fn new(account: Uuid, name: &String, constructs: Vec) -> Player { + pub fn new(account: Uuid, img: Option, name: &String, constructs: Vec) -> Player { Player { id: account, - img: Some(account), + img, name: name.clone(), vbox: Vbox::new(), constructs, @@ -401,7 +401,7 @@ mod tests { fn player_bot_vbox_test() { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); + let mut player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true); player.vbox.fill(); player.autobuy(); @@ -413,7 +413,7 @@ mod tests { fn player_score_test() { let player_account = Uuid::new_v4(); let constructs = instance_mobs(player_account); - let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); + let mut player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true); player.score = player.score.add_win(&Score::Zero); player.score = player.score.add_win(&Score::Zero); diff --git a/core/src/skill.rs b/core/src/skill.rs index 8402ff5b..13f9ab13 100644 --- a/core/src/skill.rs +++ b/core/src/skill.rs @@ -176,34 +176,23 @@ impl Cast { }, ], - // fn bash(source: &mut Construct, target: &mut Construct, skill: Skill) { - // skill.effect().into_iter() - // .for_each(|e| (game.event(Event::new(source, target).event(target.add_effect(skill, e))))); + Skill::Blast | + Skill::BlastPlus | + Skill::BlastPlusPlus => vec![ + Action::Damage { + construct: self.target, + colour: Colour::Blue, + values: vec![Value::Stat { construct: self.source, stat: Stat::BluePower, mult: self.skill.multiplier() }], + }, + ], + + + + - // if resolutions.iter().any(|r| match r.event { - // Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ } - // => effect == Effect::Stun && skill == effect_skill, - // _ => false, - // }) { - // let mut cds = 0; - // for cs in target.skills.iter_mut() { - // if cs.skill.base_cd().is_some() { - // cs.cd = match cs.cd { - // None => Some(1), - // Some(i) => Some(i + 1), - // }; - // cds += 1; - // } - // } - // let amount = source.red_power().pct(skill.multiplier().pct(100 + 45usize.saturating_mul(cds))); - // target.deal_red_damage(skill, amount) - // .into_iter() - // .for_each(|e| game.event(Event::new(source, target).event(e).stages(EventStages::PostOnly))); - // } - // } Skill::Strike | Skill::StrikePlus | @@ -1349,13 +1338,6 @@ impl Skill { // .for_each(|e| game.event(Event::new(source, target).event(e).stages(EventStages::PostOnly))); // } -// fn blast(source: &mut Construct, target: &mut Construct, skill: Skill) { -// let amount = source.blue_power().pct(skill.multiplier()); -// target.deal_blue_damage(skill, amount) -// .into_iter() -// .for_each(|e| game.event(Event::new(source, target).event(e))); -// } - // fn amplify(source: &mut Construct, target: &mut Construct, skill: Skill) { // game.event(Event::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); // } diff --git a/server/Cargo.toml b/server/Cargo.toml index 39e11d55..4b409c11 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -44,3 +44,5 @@ stripe-rust = "0.10" reqwest = "0.9" url = "1" + +mnml_core = { path = "./../core" } diff --git a/server/src/account.rs b/server/src/account.rs index c404d7d6..7373d0f6 100644 --- a/server/src/account.rs +++ b/server/src/account.rs @@ -8,19 +8,19 @@ use serde_cbor::{from_slice}; use postgres::transaction::Transaction; -use http::MnmlHttpError; -use names::{name as generate_name}; -use construct::{Construct, ConstructSkeleton, construct_spawn}; -use instance::{Instance, instance_delete}; -use instance; -use mtx::{Mtx, FREE_MTX}; -use pg::Db; -use img; - - use failure::Error; use failure::{err_msg, format_err}; +use mnml_core::construct::{Construct, ConstructSkeleton}; +use mnml_core::instance::{Instance}; +use mnml_core::player::{Player}; + +use http::MnmlHttpError; +use names::{name as generate_name}; +use mtx::{Mtx, FREE_MTX}; +use pg::{Db, instance_delete, construct_spawn, instance_practice}; +use img; + static PASSWORD_MIN_LEN: usize = 3; static PASSWORD_ROUNDS: u32 = 10; @@ -33,6 +33,19 @@ pub struct Account { pub subscribed: bool, } +impl Account { + pub fn to_player(&self, tx: &mut Transaction) -> Result { + let constructs = team(tx, self)?; + + let img = match self.subscribed { + true => Some(self.img), + false => None, + }; + + Ok(Player::new(self.id, Some(self.img), &self.name, constructs)) + } +} + impl<'a> TryFrom> for Account { type Error = Error; @@ -523,7 +536,7 @@ pub fn tutorial(tx: &mut Transaction, account: &Account) -> Result Colours { - Colours { red: 0, green: 0, blue: 0 } - } - - pub fn from_construct(construct: &Construct) -> Colours { - let mut count = Colours::new(); - - for spec in construct.specs.iter() { - let v = Item::from(*spec); - v.colours(&mut count); - } - - for cs in construct.skills.iter() { - let v = Item::from(cs.skill); - v.colours(&mut count); - } - - count - } -} - - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub struct ConstructSkill { - pub skill: Skill, - pub cd: Cooldown, - // used for Uon client - pub disabled: bool, -} - -impl ConstructSkill { - pub fn new(skill: Skill) -> ConstructSkill { - ConstructSkill { - skill, - cd: skill.base_cd(), - disabled: false, - } - } -} - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum EffectMeta { - Skill(Skill), - TickAmount(u64), - AddedDamage(u64), - LinkTarget(Uuid), - Multiplier(u64), -} - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub struct ConstructEffect { - pub effect: Effect, - pub duration: u8, - pub meta: Option, - pub tick: Option, -} - -impl ConstructEffect { - pub fn new(effect: Effect, duration: u8) -> ConstructEffect { - ConstructEffect { effect, duration, meta: None, tick: None } - } - - pub fn set_tick(mut self, tick: Cast) -> ConstructEffect { - self.tick = Some(tick); - self - } - - pub fn set_meta(mut self, meta: EffectMeta) -> ConstructEffect { - self.meta = Some(meta); - self - } - - pub fn get_duration(&self) -> u8 { - self.duration - } - - pub fn get_multiplier(&self) -> u64 { - match self.meta { - Some(EffectMeta::Multiplier(s)) => s, - _ => 0 - } - } - - pub fn get_skill(&self) -> Option { - match self.meta { - Some(EffectMeta::Skill(s)) => Some(s), - _ => None, - } - - } -} - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum Stat { - Str, - Agi, - Int, - GreenLife, - Speed, - RedPower, - BluePower, - GreenPower, - RedDamageTaken, - BlueDamageTaken, - GreenDamageTaken, - RedLife, - BlueLife, - Evasion, -} - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub struct ConstructStat { - base: u64, - value: u64, - max: u64, - pub stat: Stat, -} - -impl ConstructStat { - // pub fn set(&mut self, v: u64, specs: &Vec) -> &mut ConstructStat { - // self.base = v; - // self.recalculate(specs) - // } - - pub fn recalculate(&mut self, specs: &Vec, player_colours: &Colours) -> &mut ConstructStat { - let specs = specs - .iter() - .filter(|s| s.affects().contains(&self.stat)) - .map(|s| *s) - .collect::>(); - - // applied with fold because it can be zeroed or multiplied - // but still needs access to the base amount - let value = specs.iter().fold(self.base, |acc, s| s.apply(acc, self.base, player_colours)); - self.value = value; - self.max = value; - - self - } - - pub fn reduce(&mut self, amt: u64) -> &mut ConstructStat { - self.value = self.value.saturating_sub(amt); - self - } - - pub fn increase(&mut self, amt: u64) -> &mut ConstructStat { - self.value = *[ - self.value.saturating_add(amt), - self.max - ].iter().min().unwrap(); - - self - } - - pub fn force(&mut self, v: u64) -> &mut ConstructStat { - self.base = v; - self.value = v; - self.max = v; - - self - } -} - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct ConstructSkeleton { - pub id: Uuid, - pub account: Uuid, - pub img: Uuid, - pub name: String, -} - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct Construct { - pub id: Uuid, - pub account: Uuid, - pub img: Uuid, - pub red_power: ConstructStat, - pub red_life: ConstructStat, - pub blue_life: ConstructStat, - pub blue_power: ConstructStat, - pub green_power: ConstructStat, - pub speed: ConstructStat, - pub green_life: ConstructStat, - // pub evasion: ConstructStat, - pub skills: Vec, - pub effects: Vec, - pub specs: Vec, - pub colours: Colours, - pub name: String, -} - -impl Construct { - pub fn new() -> Construct { - let id = Uuid::new_v4(); - return Construct { - id, - account: id, - img: Uuid::new_v4(), - red_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::RedPower }, - red_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::RedLife }, - blue_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::BluePower }, - blue_life: ConstructStat { base: 125, value: 125, max: 125, stat: Stat::BlueLife }, - green_power: ConstructStat { base: 320, value: 320, max: 320, stat: Stat::GreenPower }, - green_life: ConstructStat { base: 800, value: 800, max: 800, stat: Stat::GreenLife }, - speed: ConstructStat { base: 100, value: 100, max: 100, stat: Stat::Speed }, - // evasion: ConstructStat { base: 0, value: 0, max: 0, stat: Stat::Evasion }, - skills: vec![], - effects: vec![], - specs: vec![], - colours: Colours::new(), - name: String::new(), - }; - } - - pub fn from_skeleton(skeleton: &ConstructSkeleton) -> Construct { - return Construct { - id: skeleton.id, - account: skeleton.account, - img: skeleton.img, - name: skeleton.name.clone(), - - .. Construct::new() - }; - } - - pub fn to_skeleton(&self) -> ConstructSkeleton { - ConstructSkeleton { - id: self.id, - account: self.account, - img: self.img, - name: self.name.clone(), - } - } - - - pub fn named(mut self, name: &String) -> Construct { - self.name = name.clone(); - self - } - - pub fn set_account(mut self, account: Uuid) -> Construct { - self.account = account; - self - } - - pub fn new_img(mut self) -> Construct { - self.img = Uuid::new_v4(); - self - } - - pub fn new_name(self, name: String) -> Result { - if name.len() > 20 { - return Err(err_msg("20 character name maximum")); - } - Ok(self.named(&name)) - } - - pub fn learn(mut self, s: Skill) -> Construct { - self.skills.push(ConstructSkill::new(s)); - self.colours = Colours::from_construct(&self); - self - } - - pub fn learn_mut(&mut self, s: Skill) -> &mut Construct { - self.skills.push(ConstructSkill::new(s)); - self.calculate_colours() - } - - pub fn forget(&mut self, skill: Skill) -> Result<&mut Construct, Error> { - match self.skills.iter().position(|s| s.skill == skill) { - Some(i) => { - self.skills.remove(i); - return Ok(self.calculate_colours()); - }, - None => Err(format_err!("{:?} does not know {:?}", self.name, skill)), - } - } - - pub fn spec_add(&mut self, spec: Spec) -> Result<&mut Construct, Error> { - if self.specs.len() >= 3 { - return Err(err_msg("maximum specs equipped")); - } - - self.specs.push(spec); - return Ok(self.calculate_colours()); - } - - pub fn spec_remove(&mut self, spec: Spec) -> Result<&mut Construct, Error> { - match self.specs.iter().position(|s| *s == spec) { - Some(p) => self.specs.remove(p), - None => return Err(err_msg("spec not found")), - }; - - Ok(self.calculate_colours()) - } - - fn calculate_colours(&mut self) -> &mut Construct { - self.colours = Colours::from_construct(&self); - self - } - - pub fn apply_modifiers(&mut self, player_colours: &Colours) -> &mut Construct { - self.red_power.recalculate(&self.specs, player_colours); - self.red_life.recalculate(&self.specs, player_colours); - self.blue_power.recalculate(&self.specs, player_colours); - self.blue_life.recalculate(&self.specs, player_colours); - // self.evasion.recalculate(&self.specs, &self.colours, player_colours); - self.speed.recalculate(&self.specs, player_colours); - self.green_power.recalculate(&self.specs, player_colours); - self.green_life.recalculate(&self.specs, player_colours); - - self - } - - pub fn is_ko(&self) -> bool { - self.green_life.value == 0 - } - - pub fn force_ko(&mut self) -> &mut Construct { - self.green_life.value = 0; - self - } - - pub fn immune(&self, skill: Skill) -> Option { - // also checked in resolve stage so shouldn't happen really - if self.is_ko() { - return Some(vec![Effect::Ko]); - } - - let immunities = self.effects.iter() - .filter(|e| e.effect.immune(skill)) - .map(|e| e.effect) - .collect::>(); - - if immunities.len() > 0 { - return Some(immunities); - } - - None - } - - pub fn disabled(&self, skill: Skill) -> Option { - if self.is_ko() && !skill.ko_castable() { - return Some(vec![Effect::Ko]); - } - - let disables = self.effects.iter() - .filter(|e| e.effect.disables_skill(skill)) - .map(|e| e.effect) - .collect::>(); - - if disables.len() > 0 { - return Some(disables); - } - - None - } - - pub fn is_stunned(&self) -> bool { - self.available_skills().len() == 0 - } - - pub fn affected(&self, effect: Effect) -> bool { - self.effects.iter().any(|s| s.effect == effect) - } - - pub fn available_skills(&self) -> Vec<&ConstructSkill> { - self.skills.iter() - .filter(|s| s.cd.is_none()) - .filter(|s| self.disabled(s.skill).is_none()) - .collect() - } - - pub fn mob_select_skill(&self) -> Option { - let available = self.available_skills(); - - if available.len() == 0 { - return None; - } - - let mut rng = thread_rng(); - - let i = match available.len() { - 1 => 0, - _ => rng.gen_range(0, available.len()), - }; - - return Some(available[i].skill); - - // let highest_cd = available.iter() - // .filter(|s| s.skill.base_cd().is_some()) - // .max_by_key(|s| s.skill.base_cd().unwrap()); - - // return match highest_cd { - // Some(s) => Some(s.skill), - // None => Some(available[0].skill), - // }; - } - - pub fn knows(&self, skill: Skill) -> bool { - self.skills.iter().any(|s| s.skill == skill) - } - - pub fn skill_on_cd(&self, skill: Skill) -> Option<&ConstructSkill> { - self.skills.iter().find(|s| s.skill == skill && s.cd.is_some()) - } - - pub fn skill_set_cd(&mut self, skill: Skill) -> &mut Construct { - let i = self.skills.iter().position(|s| s.skill == skill).unwrap(); - self.skills.remove(i); - self.skills.push(ConstructSkill::new(skill)); - - self - } - - pub fn reduce_cooldowns(&mut self) -> &mut Construct { - for skill in self.skills.iter_mut() { - // if used cooldown - if skill.skill.base_cd().is_some() { - // what is the current cd - if let Some(current_cd) = skill.cd { - - // if it's 1 set it to none - if current_cd == 1 { - skill.cd = None; - continue; - } - - // otherwise decrement it - skill.cd = Some(current_cd.saturating_sub(1)); - } - - } - } - - self - } - - pub fn reduce_effect_durations(&mut self) -> &mut Construct { - self.effects = self.effects.clone().into_iter().filter_map(|mut effect| { - effect.duration = effect.duration.saturating_sub(1); - - if effect.duration == 0 { - return None; - } - - // info!("reduced effect {:?}", effect); - return Some(effect); - }).collect::>(); - - self - } - - // Stats - pub fn red_power(&self) -> u64 { - let red_power_mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::RedPower)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let modified_red_power = red_power_mods.iter() - .fold(self.red_power.value, |acc, fx| fx.0.apply(acc, fx.1)); - return modified_red_power; - } - - pub fn blue_power(&self) -> u64 { - let blue_power_mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::BluePower)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let modified_blue_power = blue_power_mods.iter() - .fold(self.blue_power.value, |acc, fx| fx.0.apply(acc, fx.1)); - return modified_blue_power; - } - - pub fn green_power(&self) -> u64 { - let green_power_mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::GreenPower)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let modified_green_power = green_power_mods.iter() - .fold(self.green_power.value, |acc, fx| fx.0.apply(acc, fx.1)); - return modified_green_power; - } - - pub fn skill_speed(&self, s: Skill) -> u64 { - self.speed().saturating_mul(s.speed() as u64) - } - - // todo complete with specs - pub fn skill_is_aoe(&self, s: Skill) -> bool { - s.aoe() - } - - pub fn speed(&self) -> u64 { - let speed_mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::Speed)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let modified_speed = speed_mods.iter() - .fold(self.speed.value, |acc, fx| fx.0.apply(acc, fx.1)); - return modified_speed; - } - - pub fn red_life(&self) -> u64 { - self.red_life.value - } - - pub fn blue_life(&self) -> u64 { - self.blue_life.value - } - - pub fn green_life(&self) -> u64 { - self.green_life.value - } - - fn reduce_green_life(&mut self, amount: u64) { - self.green_life.reduce(amount); - if self.affected(Effect::Sustain) && self.green_life() == 0 { - self.green_life.value = 1; - } - } - - pub fn recharge(&mut self, skill: Skill, red_amount: u64, blue_amount: u64) -> Vec { - let mut events = vec![]; - - // Should red type immunity block recharge??? - if let Some(immunity) = self.immune(skill) { - if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); - } - return events; - } - - match self.affected(Effect::Invert) { - false => { - // Do we need inversion? - let current_red_life = self.red_life(); - self.red_life.increase(red_amount); - let new_red_life = self.red_life.value; - let red = new_red_life - current_red_life; - - let current_blue_life = self.blue_life(); - self.blue_life.increase(blue_amount); - let new_blue_life = self.blue_life.value; - let blue = new_blue_life - current_blue_life; - - if red != 0 || blue != 0 { - events.push(Event::Recharge { red, blue, skill }); - } - }, - true => { - // Recharge takes a red and blue amount so check for them - if red_amount != 0 { - let red_mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::RedDamageTaken)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let red_modified_power = red_mods.iter() - .fold(red_amount, |acc, fx| fx.0.apply(acc, fx.1)); - - - let red_remainder = red_modified_power.saturating_sub(self.red_life.value); - let red_mitigation = red_modified_power.saturating_sub(red_remainder); - - // reduce red_life by mitigation amount - self.red_life.reduce(red_mitigation); - - // deal remainder to green_life - let red_current_green_life = self.green_life(); - self.reduce_green_life(red_remainder); - let red_damage_amount = red_current_green_life - self.green_life(); - - events.push(Event::Damage { - skill, - amount: red_damage_amount, - mitigation: red_mitigation, - colour: Colour::Red - }); - } - - if blue_amount != 0 { - let blue_mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::BlueDamageTaken)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let blue_modified_power = blue_mods.iter() - .fold(blue_amount, |acc, fx| fx.0.apply(acc, fx.1)); - - - let blue_remainder = blue_modified_power.saturating_sub(self.blue_life.value); - let blue_mitigation = blue_modified_power.saturating_sub(blue_remainder); - - // reduce blue_life by mitigation amount - self.blue_life.reduce(blue_mitigation); - - // deal remainder to green_life - let blue_current_green_life = self.green_life(); - self.reduce_green_life(blue_remainder); - let blue_damage_amount = blue_current_green_life - self.green_life(); - - events.push(Event::Damage { - skill, - amount: blue_damage_amount, - mitigation: blue_mitigation, - colour: Colour::Blue - }); - } - } - } - return events; - } - - pub fn deal_green_damage(&mut self, skill: Skill, amount: u64) -> Vec { - let mut events = vec![]; - if let Some(immunity) = self.immune(skill) { - if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); - } - return events; - } - - let mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::GreenDamageTaken)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let modified_power = mods.iter() - .fold(amount, |acc, fx| fx.0.apply(acc, fx.1)); - - match self.affected(Effect::Invert) { - false => { - let current_green_life = self.green_life(); - self.green_life.increase(modified_power); - let new_green_life = self.green_life.value; - - let healing = new_green_life - current_green_life; - let overhealing = modified_power - healing; - - events.push(Event::Healing { - skill, - amount: healing, - overhealing, - }); - }, - true => { - // events.push(Event::Inversion { skill }); - - // there is no green shield (yet) - let current_green_life = self.green_life(); - self.reduce_green_life(modified_power); - let delta = current_green_life - self.green_life(); - - events.push(Event::Damage { - skill, - amount: delta, - mitigation: 0, - colour: Colour::Green, - }); - } - } - - return events; - } - - pub fn deal_red_damage(&mut self, skill: Skill, amount: u64) -> Vec { - let mut events = vec![]; - - if let Some(immunity) = self.immune(skill) { - if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); - } - return events; - } - - let mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::RedDamageTaken)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let modified_power = mods.iter() - .fold(amount, |acc, fx| fx.0.apply(acc, fx.1)); - - match self.affected(Effect::Invert) { - false => { - // calculate amount of damage red_life will not absorb - // eg 50 red_life 25 damage -> 0 remainder 25 mitigation - // 50 red_life 100 damage -> 50 remainder 50 mitigation - // 50 red_life 5 damage -> 0 remainder 5 mitigation - let remainder = modified_power.saturating_sub(self.red_life.value); - let mitigation = modified_power.saturating_sub(remainder); - - // reduce red_life by mitigation amount - self.red_life.reduce(mitigation); - - // deal remainder to green_life - let current_green_life = self.green_life(); - self.reduce_green_life(remainder); - let delta = current_green_life - self.green_life(); - - events.push(Event::Damage { - skill, - amount: delta, - mitigation, - colour: Colour::Red, - }); - }, - true => { - // events.push(Event::Inversion { skill }); - - let current_green_life = self.green_life(); - self.green_life.increase(modified_power); - let new_green_life = self.green_life.value; - let healing = new_green_life - current_green_life; - let overhealing = modified_power - healing; - - let current_life = self.red_life.value; - self.red_life.increase(overhealing); - let recharge = self.red_life.value - current_life; - - if healing > 0 { - events.push(Event::Healing { - skill, - amount: healing, - overhealing: overhealing - recharge, - }); - } - - if recharge > 0 { - events.push(Event::Recharge { red: recharge, blue: 0, skill }); - } - } - }; - - return events; - } - - pub fn deal_blue_damage(&mut self, skill: Skill, amount: u64) -> Vec { - let mut events = vec![]; - - if let Some(immunity) = self.immune(skill) { - if !self.is_ko() { - events.push(Event::Immunity { skill, immunity }); - } - return events; - } - - let mods = self.effects.iter() - .filter(|e| e.effect.modifications().contains(&Stat::BlueDamageTaken)) - .map(|e| (e.effect, e.meta)) - .collect::)>>(); - - let modified_power = mods.iter() - .fold(amount, |acc, fx| fx.0.apply(acc, fx.1)); - - match self.affected(Effect::Invert) { - false => { - let remainder = modified_power.saturating_sub(self.blue_life.value); - let mitigation = modified_power.saturating_sub(remainder); - - // reduce blue_life by mitigation amount - self.blue_life.reduce(mitigation); - - // deal remainder to green_life - let current_green_life = self.green_life(); - self.reduce_green_life(remainder); - let delta = current_green_life - self.green_life(); - - events.push(Event::Damage { - skill, - amount: delta, - mitigation, - colour: Colour::Blue, - }); - }, - true => { - // events.push(Event::Inversion { skill }); - - let current_green_life = self.green_life(); - self.green_life.increase(modified_power); - let new_green_life = self.green_life.value; - let healing = new_green_life - current_green_life; - let overhealing = modified_power - healing; - - let current_life = self.blue_life.value; - self.blue_life.increase(overhealing); - let recharge = self.blue_life.value - current_life; - - if healing > 0 { - events.push(Event::Healing { - skill, - amount: healing, - overhealing, - }); - } - - if recharge > 0 { - events.push(Event::Recharge { red: 0, blue: recharge, skill }); - } - } - }; - - return events; - } - - pub fn add_effect(&mut self, skill: Skill, effect: ConstructEffect) -> Event { - if let Some(immunity) = self.immune(skill) { - return Event::Immunity { - skill, - immunity, - }; - } - - if let Some(p) = self.effects.iter().position(|ce| ce.effect == effect.effect) { - // duplicate effect - // replace existing - - self.effects[p] = effect; - } else { - // new effect - // info!("{:?} {:?} adding effect", self.name, effect.effect); - self.effects.push(effect); - } - - // todo modified durations cause of buffs - let result = Event::Effect { - effect: effect.effect, - duration: effect.duration, - construct_effects: self.effects.clone(), - skill, - }; - return result; - } - - // pub fn evade(&self, skill: Skill) -> Option { - // if self.evasion.value == 0 { - // return None; - // } - - // let mut rng = thread_rng(); - // let green_life_pct = (self.green_life.value * 100) / self.green_life.value; - // let evasion_rating = (self.evasion.value * green_life_pct) / 100; - // let roll = rng.gen_range(0, 100); - // info!("{:} < {:?}", roll, evasion_rating); - - // match roll < evasion_rating { - // true => Some(Event::Evasion { - // skill, - // evasion_rating: evasion_rating, - // }), - // false => None, - // } - // } -} - -pub fn construct_delete(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<(), Error> { - let query = " - DELETE - FROM constructs - WHERE id = $1 - and account = $2; - "; - - let result = tx - .execute(query, &[&id, &account_id])?; - - if result != 1 { - return Err(format_err!("unable to delete construct {:?}", id)); - } - - info!("construct deleted {:?}", id); - - return Ok(()); -} - -pub fn construct_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result { - let query = " - SELECT data - FROM constructs - WHERE id = $1 - AND account = $2; - "; - - let result = tx - .query(query, &[&id, &account_id])?; - - let result = result.iter().next().ok_or(format_err!("construct {:} not found", id))?; - let construct_bytes: Vec = result.get(0); - let skeleton = from_slice::(&construct_bytes)?; - - return Ok(Construct::from_skeleton(&skeleton)); -} - -pub fn construct_select(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result { - let query = " - SELECT data - FROM constructs - WHERE id = $1 - AND account = $2 - FOR UPDATE; - "; - - let result = tx - .query(query, &[&id, &account_id])?; - - let result = result.iter().next().ok_or(format_err!("construct {:} not found", id))?; - let construct_bytes: Vec = result.get(0); - let skeleton = from_slice::(&construct_bytes)?; - - return Ok(Construct::from_skeleton(&skeleton)); -} - -pub fn construct_spawn(tx: &mut Transaction, account: Uuid, name: String, team: bool) -> Result { - let construct = Construct::new() - .named(&name) - .set_account(account); - - let construct_bytes = to_vec(&construct)?; - - let query = " - INSERT INTO constructs (id, account, data, team) - VALUES ($1, $2, $3, $4) - RETURNING id, account; - "; - - let result = tx - .query(query, &[&construct.id, &account, &construct_bytes, &team])?; - - let _returned = result.iter().next().ok_or(err_msg("no row returned"))?; - - img::shapes_write(construct.img)?; - - info!("spawned construct account={:} name={:?}", account, construct.name); - return Ok(construct); -} - -pub fn construct_write(tx: &mut Transaction, construct: Construct) -> Result { - let construct_bytes = to_vec(&construct.to_skeleton())?; - - let query = " - UPDATE constructs - SET data = $1, updated_at = now() - WHERE id = $2 - RETURNING id, account, data; - "; - - let result = tx - .query(query, &[&construct_bytes, &construct.id])?; - - let _returned = result.iter().next().expect("no row returned"); - - // info!("{:?} wrote construct", construct.id); - - return Ok(construct); -} - -#[cfg(test)] -mod tests { - use construct::*; - use util::IntPct; - - #[test] - fn create_construct_test() { - let construct = Construct::new() - .named(&"hatchling".to_string()); - - assert_eq!(construct.name, "hatchling".to_string()); - return; - } - - #[test] - fn construct_colours_test() { - let mut construct = Construct::new() - .named(&"redboi".to_string()); - - construct.learn_mut(Skill::Strike); - construct.spec_add(Spec::LifeGG).unwrap(); - construct.spec_add(Spec::PowerRR).unwrap(); - construct.spec_add(Spec::LifeBB).unwrap(); - - assert_eq!(construct.colours.red, 4); - assert_eq!(construct.colours.green, 2); - assert_eq!(construct.colours.blue, 2); - - return; - } - - #[test] - fn construct_player_modifiers_test() { - let mut construct = Construct::new() - .named(&"player player".to_string()); - - construct.spec_add(Spec::PowerRR).unwrap(); - construct.spec_add(Spec::PowerGG).unwrap(); - construct.spec_add(Spec::PowerBB).unwrap(); - construct.learn_mut(Skill::StrikePlusPlus); // 18 reds (24 total) - - let player_colours = Colours { - red: 5, - green: 15, - blue: 25, - }; - - construct.apply_modifiers(&player_colours); - - assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(15)); - assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(24)); - assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(37)); - - return; - } - - #[test] - fn construct_player_modifiers_base_test() { - let mut construct = Construct::new() - .named(&"player player".to_string()); - - construct.spec_add(Spec::Power).unwrap(); - construct.spec_add(Spec::Life).unwrap(); - - let player_colours = Colours { - red: 5, - green: 15, - blue: 25, - }; - - construct.apply_modifiers(&player_colours); - - assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(10)); - assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(10)); - assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(10)); - assert!(construct.green_life.value == construct.green_life.base + 125); - - return; - } - - #[test] - fn construct_colour_calc_test() { - let mut construct = Construct::new() - .named(&"player player".to_string()); - - construct.spec_add(Spec::PowerRR).unwrap(); - construct.spec_add(Spec::PowerGG).unwrap(); - construct.spec_add(Spec::PowerBB).unwrap(); - - let colours = Colours::from_construct(&construct); - assert!(colours.red == 2); - assert!(colours.blue == 2); - assert!(colours.green == 2); - - let construct = construct - .learn(Skill::Strike) - .learn(Skill::BlastPlusPlus); - - let colours = Colours::from_construct(&construct); - assert!(colours.red == 4); - assert!(colours.blue == 10); - assert!(colours.green == 2); - } - - #[test] - fn construct_player_modifiers_spec_bonus_test() { - let mut construct = Construct::new() - .named(&"player player".to_string()); - - construct.spec_add(Spec::PowerRR).unwrap(); - construct.spec_add(Spec::PowerGG).unwrap(); - construct.spec_add(Spec::PowerBB).unwrap(); - - let player_colours = Colours { - red: 5, - green: 0, - blue: 0, - }; - - construct.apply_modifiers(&player_colours); - - assert!(construct.red_power.value == construct.red_power.base + construct.red_power.base.pct(15)); - assert!(construct.green_power.value == construct.green_power.base + construct.green_power.base.pct(10)); - assert!(construct.blue_power.value == construct.blue_power.base + construct.blue_power.base.pct(10)); - - return; - } - -} diff --git a/server/src/effect.rs b/server/src/effect.rs deleted file mode 100644 index e1a87604..00000000 --- a/server/src/effect.rs +++ /dev/null @@ -1,209 +0,0 @@ -use construct::{Stat, EffectMeta}; -use skill::{Skill}; -use util::{IntPct}; - -pub type Cooldown = Option; - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum Effect { - Amplify, - Banish, - Block, - Buff, - Counter, - Curse, - Haste, - Hybrid, - Intercept, - Invert, - Pure, - Purge, - Reflect, - Restrict, - Silence, - Slow, - Stun, - Sustain, - Vulnerable, - Wither, // Reduce green dmg (healing) taken - - // electric is the buff that applies - // electrocute the dmg debuff - Electric, - Electrocute, - - // absorbtion is the buff - // absorb is the increased damage - Absorb, - Absorption, - - // magic immunity - - // effects over time - Triage, - Decay, - Regen, - Siphon, - - // Airborne, - // Boost - // Bleed, - // Blind, - // Deadly, - // Enslave, - // Fury, - // Injured, - // Leech, - // Mesmerise, - // Untouchable, - // SpeedSiphon, - // SpeedIncrease, - - Ko, -} - -impl Effect { - pub fn immune(&self, skill: Skill) -> bool { - match self { - Effect::Banish => true, - Effect::Sustain => [ - Skill::Stun, - Skill::Silence, - Skill::SilencePlus, - Skill::SilencePlusPlus, - Skill::Ruin, - Skill::RuinPlus, - Skill::RuinPlusPlus, - Skill::Restrict, - Skill::RestrictPlus, - Skill::RestrictPlusPlus - ].contains(&skill), - _ => false, - } - } - - pub fn disables_skill(&self, skill: Skill) -> bool { - if skill.is_tick() { - return false; - } - - match self { - Effect::Stun => true, - Effect::Banish => true, - Effect::Silence => skill.colours().contains(&Colour::Blue), - Effect::Restrict => skill.colours().contains(&Colour::Red), - Effect::Purge => skill.colours().contains(&Colour::Green), - Effect::Ko => skill.ko_castable(), - _ => false, - } - } - - pub fn modifications(&self) -> Vec { - match self { - // Bases - Effect::Block => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken], - Effect::Buff => vec![Stat::BluePower, Stat::RedPower, Stat::Speed], - Effect::Slow => vec![Stat::Speed], - - // Power changes - Effect::Absorption => vec![Stat::RedPower, Stat::BluePower], - Effect::Amplify => vec![Stat::RedPower, Stat::BluePower], - Effect::Hybrid => vec![Stat::GreenPower], - - // Damage taken changes - Effect::Curse => vec![Stat::RedDamageTaken, Stat::BlueDamageTaken], - Effect::Pure => vec![Stat::GreenDamageTaken], // increased green taken - Effect::Vulnerable => vec![Stat::RedDamageTaken], - Effect::Wither => vec![Stat::GreenDamageTaken], // reduced green taken - - // Speed - Effect::Haste => vec![Stat::Speed], - - _ => vec![], - } - } - - pub fn apply(&self, value: u64, meta: Option) -> u64 { - match self { - Effect::Amplify | - Effect::Vulnerable | - Effect::Block | - Effect::Buff | - Effect::Curse | - Effect::Haste | - Effect::Slow | - Effect::Hybrid | - Effect::Pure | - Effect::Wither => value.pct(match meta { - Some(EffectMeta::Multiplier(d)) => d, - _ => 100, - }), - - Effect::Absorption => value + match meta { - Some(EffectMeta::AddedDamage(d)) => d, - _ => { - warn!("absorb meta not damage"); - return 0; - } - }, - - _ => { - warn!("{:?} does not have a mod effect", self); - return value; - }, - } - } - - pub fn colour(&self) -> Option { - match self { - // physical - Effect::Stun => Some(Colour::Red), - Effect::Block => Some(Colour::Green), - Effect::Buff => Some(Colour::Green), - Effect::Counter => Some(Colour::Green), - Effect::Vulnerable => Some(Colour::Red), - Effect::Restrict => Some(Colour::Red), - Effect::Sustain => Some(Colour::Green), - Effect::Intercept => Some(Colour::Green), - - // magic - Effect::Curse => Some(Colour::Blue), - Effect::Banish => None, - // Effect::Banish => rng.gen_bool(0.5), - - Effect::Slow => Some(Colour::Blue), - Effect::Haste => Some(Colour::Green), - Effect::Absorption => Some(Colour::Green), - Effect::Reflect => Some(Colour::Green), - Effect::Amplify => Some(Colour::Green), - Effect::Silence => Some(Colour::Blue), - Effect::Wither => Some(Colour::Blue), - Effect::Purge => Some(Colour::Blue), - - Effect::Electric => Some(Colour::Green), - Effect::Electrocute => Some(Colour::Blue), - - Effect::Absorb => Some(Colour::Green), - - // magic - Effect::Hybrid => Some(Colour::Green), - Effect::Invert => Some(Colour::Green), - - // effects over time - Effect::Triage => Some(Colour::Green), - Effect::Decay => Some(Colour::Blue), - Effect::Regen => Some(Colour::Green), - Effect::Siphon => Some(Colour::Blue), - Effect::Pure => Some(Colour::Green), - - Effect::Ko => None, - } - } -} - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum Colour { - Red, - Blue, - Green, -} diff --git a/server/src/error.rs b/server/src/error.rs deleted file mode 100644 index e09bac41..00000000 --- a/server/src/error.rs +++ /dev/null @@ -1,107 +0,0 @@ -// This example illustrates the error flow of a Request in the middleware Chain. -// Here is the chain used and the path of the request through the middleware pieces: -// -// Normal Flow : __[ErrorProducer::before]__ [ErrorRecover::before] __[handle::HelloWorldHandler]__[ErrorProducer::after]__ [ErrorRecover::after] __ ... -// Error Flow : [ErrorProducer::catch ] |__[ErrorRecover::catch ]__| [ErrorProducer::catch] |__[ErrorRecover::catch]__| -// -// --------------- BEFORE MIDDLEWARE ----------------- || --------- HANDLER -------- || ---------------- AFTER MIDDLEWARE -------------- - -extern crate iron; - -use iron::prelude::*; -use iron::StatusCode; -use iron::{AfterMiddleware, BeforeMiddleware, Handler}; - -use std::error::Error; -use std::fmt::{self, Debug}; - -struct HelloWorldHandler; -struct ErrorProducer; -struct ErrorRecover; - -#[derive(Debug)] -struct StringError(String); - -impl fmt::Display for StringError { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - Debug::fmt(self, f) - } -} - -impl Error for StringError { - fn description(&self) -> &str { - &*self.0 - } -} - -impl Handler for HelloWorldHandler { - fn handle(&self, _: &mut Request) -> IronResult { - // This will be called since we are in the normal flow before reaching the Handler. - // However, the AfterMiddleware chain will override the Response. - println!("The HelloWorldHandler has been called !"); - Ok(Response::with((StatusCode::OK, "Hello world !"))) - } -} - -impl BeforeMiddleware for ErrorProducer { - fn before(&self, _: &mut Request) -> IronResult<()> { - // The error produced here switches to the error flow. - // The catch method of following middleware pieces will be called. - // The Handler will be skipped unless the error is handled by another middleware piece. - // IronError::error tells the next middleware what went wrong. - // IronError::response is the Response that will be sent back to the client if this error is not handled. - // Here status::BadRequest acts as modifier, thus we can put more there than just a status. - Err(IronError::new( - StringError("Error in ErrorProducer BeforeMiddleware".to_string()), - StatusCode::BAD_REQUEST, - )) - } -} - -impl AfterMiddleware for ErrorProducer { - fn after(&self, _: &mut Request, _: Response) -> IronResult { - // The behavior here is the same as in ErrorProducer::before. - // The previous response (from the Handler) is discarded and replaced with a new response (created from the modifier). - Err(IronError::new( - StringError("Error in ErrorProducer AfterMiddleware".to_string()), - (StatusCode::BAD_REQUEST, "Response created in ErrorProducer"), - )) - } -} - -impl BeforeMiddleware for ErrorRecover { - fn catch(&self, _: &mut Request, err: IronError) -> IronResult<()> { - // We can use the IronError from previous middleware to decide what to do. - // Returning Ok() from a catch method resumes the normal flow and - // passes the Request forward to the next middleware piece in the chain (here the HelloWorldHandler). - println!("{} caught in ErrorRecover BeforeMiddleware.", err.error); - match err.response.status { - Some(StatusCode::BAD_REQUEST) => Ok(()), - _ => Err(err), - } - } -} - -impl AfterMiddleware for ErrorRecover { - fn catch(&self, _: &mut Request, err: IronError) -> IronResult { - // Just like in the BeforeMiddleware, we can return Ok(Response) here to return to the normal flow. - // In this case, ErrorRecover is the last middleware in the chain - // and the Response created in the ErrorProducer is modified and sent back to the client. - println!("{} caught in ErrorRecover AfterMiddleware.", err.error); - match err.response.status { - Some(StatusCode::BAD_REQUEST) => Ok(err.response.set(StatusCode::OK)), - _ => Err(err), - } - } -} - -fn main() { - let mut chain = Chain::new(HelloWorldHandler); - chain.link_before(ErrorProducer); - chain.link_before(ErrorRecover); - - chain.link_after(ErrorProducer); - chain.link_after(ErrorRecover); - - Iron::new(chain).http("localhost:3000"); -} diff --git a/server/src/events.rs b/server/src/events.rs index fef6d71c..0a347868 100644 --- a/server/src/events.rs +++ b/server/src/events.rs @@ -10,13 +10,9 @@ use failure::{err_msg, format_err}; use crossbeam_channel::{Sender, Receiver}; -use account; use account::Account; -use game; -use instance; use names; -use pg::{Db, PgPool}; use rpc::RpcMessage; use warden::{GameEvent}; use mail::Mail; diff --git a/server/src/game.rs b/server/src/game.rs deleted file mode 100644 index 8d3ed833..00000000 --- a/server/src/game.rs +++ /dev/null @@ -1,1557 +0,0 @@ -use std::fs::File; - -use rand::prelude::*; -use uuid::Uuid; - -// timekeeping -use chrono::prelude::*; -use chrono::Duration; - -// Db Commons -use serde_cbor::{from_slice, to_vec}; -use postgres::transaction::Transaction; -use failure::Error; -use failure::err_msg; - -use account::Account; -use pg::Db; - -use construct::{Construct}; -use skill::{Skill, Cast, Resolution, Event, resolve}; -use effect::{Effect}; -use player::{Player}; -use instance::{TimeControl, instance_game_finished}; - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum Phase { - Start, - Skill, - Resolve, - Finished, -} - -#[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 resolved: Vec, - pub instance: Option, - time_control: TimeControl, - phase_start: DateTime, - 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![], - resolved: 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.source_player_id == 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.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 - } - - fn add_skill(&mut self, player_id: Uuid, source_construct_id: Uuid, target_construct_id: 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_construct_id) { - 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_construct_id) { - 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_construct_id == source_construct_id) { - self.stack.remove(s); - } - - let skill = Cast::new(source_construct_id, player_id, target_construct_id, skill); - self.stack.push(skill); - - return Ok(self); - } - - 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); - } - - 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()); - } - - - 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_construct_id).unwrap().account != player_id); - - return Ok(self); - } - - 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) - } - - 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.source_player_id == t.id).collect::>() - // // should equal the number required this turn - // .len() == t.skills_required() - // ) - } - - // requires no input - // just do it - fn resolve_phase_start(mut self) -> Game { - if self.phase != Phase::Skill { - panic!("game not in skill phase"); - } - assert!(self.skill_phase_finished()); - - self.phase = Phase::Resolve; - // self.log.push("".to_string()); - - self.resolve_skills() - } - - 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_construct_id).unwrap(); - let speed = caster.skill_speed(s.skill); - s.speed = speed; - } - }); - - sorted.sort_unstable_by_key(|s| s.speed); - - self.stack = sorted; - - self - } - - fn construct_aoe_targets(&self, construct_id: Uuid) -> Vec { - self.players.iter() - .find(|t| t.constructs.iter().any(|c| c.id == construct_id)) - .unwrap() - .constructs - .iter() - .map(|c| c.id) - .collect() - } - - pub fn get_targets(&self, skill: Skill, source: &Construct, target_construct_id: Uuid) -> Vec { - let target_player = self.players.iter() - .find(|t| t.constructs.iter().any(|c| c.id == target_construct_id)) - .unwrap(); - - if let Some(t) = target_player.intercepting() { - return vec![t.id]; - } - - match source.skill_is_aoe(skill) { - true => self.construct_aoe_targets(target_construct_id), - false => vec![target_construct_id], - } - } - - fn resolve_skills(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 casts = vec![]; - let mut r_animation_ms = 0; - while let Some(cast) = self.stack.pop() { - // info!("{:} casts ", cast); - - let mut resolutions = resolve(&cast, &mut self); - r_animation_ms = resolutions.iter().fold(r_animation_ms, |acc, r| acc + r.clone().get_delay()); - - - // the cast itself goes into this temp vec to handle cooldowns - // if theres no resolution events, the skill didn't trigger (disable etc) - if resolutions.len() > 0 { - casts.push(cast); - } - - self.resolved.append(&mut resolutions); - - // while let Some(resolution) = resolutions.pop() { - // self.log_resolution(cast.speed, &resolution); - // // the results go into the resolutions - // self.resolved.push(resolution); - // } - - // sort the stack again in case speeds have changed - self.stack_sort_speed(); - }; - - // info!("{:#?}", self.casts); - - // handle cooldowns and statuses - self.progress_durations(&casts); - - if self.finished() { - return self.finish() - } - - self.skill_phase_start(r_animation_ms) - } - - fn progress_durations(&mut self, resolved: &Vec) -> &mut Game { - for mut construct in self.all_constructs() { - // info!("progressing durations for {:}", construct.name); - - if construct.is_ko() { - continue; - } - - // only reduce cooldowns if no cd was used - { - if let Some(skill) = resolved.iter() - .filter(|s| s.source_construct_id == construct.id) - .find(|s| s.used_cooldown()) { - construct.skill_set_cd(skill.skill); - } else { - construct.reduce_cooldowns(); - } - } - - // always reduce durations - construct.reduce_effect_durations(); - self.update_construct(&mut construct); - } - - self - } - - // fn log_resolution(&mut self, speed: u64, resolution: &Resolution) -> &mut Game { - // let Resolution { 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.resolved.push(forfeit) - // // self.log.push(format!("{:} forfeited.", player.name)); - // } - } - } - - self = self.resolve_phase_start(); - self - } -} - -pub fn game_write(tx: &mut Transaction, game: &Game) -> Result<(), Error> { - let game_bytes = to_vec(&game)?; - - let query = " - INSERT INTO games (id, data, upkeep) - VALUES ($1, $2, $3) - RETURNING id; - "; - - // no games should be sent to db that are not in progress - let result = tx - .query(query, &[&game.id, &game_bytes, &game.phase_end])?; - - result.iter().next().ok_or(format_err!("no game written"))?; - - // info!("{:} wrote game", game.id); - - return Ok(()); -} - -pub fn game_state(tx: &mut Transaction, account: &Account, id: Uuid) -> Result { - Ok(game_get(tx, id)?.redact(account.id)) -} - -pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result { - let query = " - SELECT * - FROM games - WHERE id = $1 - FOR UPDATE; - "; - - let result = tx - .query(query, &[&id])?; - - let returned = match result.iter().next() { - Some(row) => row, - None => return Err(err_msg("game not found")), - }; - - // tells from_slice to cast into a construct - let game_bytes: Vec = returned.get("data"); - let game = from_slice::(&game_bytes)?; - - return Ok(game); -} - -pub fn select(db: &Db, id: Uuid) -> Result { - let query = " - SELECT * - FROM games - WHERE id = $1; - "; - - let result = db - .query(query, &[&id])?; - - let returned = match result.iter().next() { - Some(row) => row, - None => return Err(err_msg("game not found")), - }; - - // tells from_slice to cast into a construct - let game_bytes: Vec = returned.get("data"); - let game = from_slice::(&game_bytes)?; - - return Ok(game); -} - -pub fn list(db: &Db, number: u32) -> Result, Error> { - let query = " - SELECT data - FROM games - ORDER BY created_at - LIMIT $1; - "; - - let result = db - .query(query, &[&number])?; - - let mut list = vec![]; - - for row in result.into_iter() { - let bytes: Vec = row.get(0); - - match from_slice::(&bytes) { - Ok(i) => list.push(i), - Err(e) => { - warn!("{:?}", e); - } - }; - } - - return Ok(list); -} - -pub fn games_need_upkeep(tx: &mut Transaction) -> Result, Error> { - let query = " - SELECT data, id - FROM games - WHERE finished = false - AND upkeep < now() - FOR UPDATE; - "; - let result = tx - .query(query, &[])?; - - let mut list = vec![]; - - for row in result.into_iter() { - let bytes: Vec = row.get(0); - let id = row.get(1); - - match from_slice::(&bytes) { - Ok(i) => list.push(i), - Err(_e) => { - game_delete(tx, id)?; - } - }; - } - - return Ok(list); -} - -pub fn game_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { - let query = " - DELETE - FROM games - WHERE id = $1; - "; - - let result = tx - .execute(query, &[&id])?; - - if result != 1 { - return Err(format_err!("unable to delete player {:?}", id)); - } - - info!("game deleted {:?}", id); - - return Ok(()); -} - -pub fn game_update(tx: &mut Transaction, game: &Game) -> Result<(), Error> { - let game_bytes = to_vec(&game)?; - - let query = " - UPDATE games - SET data = $1, finished = $2, upkeep = $3, updated_at = now() - WHERE id = $4 - RETURNING id, data; - "; - - let result = tx - .query(query, &[&game_bytes, &game.finished(), &game.phase_end, &game.id])?; - - result.iter().next().ok_or(format_err!("game {:?} could not be written", game))?; - - if game.finished() { - info!("finished id={:?}", game.id); - - match game_json_file_write(&game) { - Ok(dest) => info!("wrote dest={:?}", dest), - Err(e) => error!("json write error={:?}", e), - }; - - if let Some(i) = game.instance { - instance_game_finished(tx, &game, i)?; - } - } - - return Ok(()); -} - -fn game_json_file_write(g: &Game) -> Result { - let dest = format!("/var/lib/mnml/data/games/{}.mnml.game.json", g.id); - serde_json::to_writer(File::create(&dest)?, g)?; - Ok(dest) -} - -pub fn game_skill(tx: &mut Transaction, account: &Account, game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result { - let mut game = game_get(tx, game_id)?; - - game.add_skill(account.id, construct_id, target_construct_id, skill)?; - - if game.skill_phase_finished() { - game = game.resolve_phase_start(); - } - - game_update(tx, &game)?; - - Ok(game) -} - -pub fn game_offer_draw(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { - let game = game_get(tx, game_id)? - .offer_draw(account.id)?; - - game_update(tx, &game)?; - - Ok(game) -} - -pub fn game_concede(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { - let game = game_get(tx, game_id)? - .concede(account.id)?; - - game_update(tx, &game)?; - - Ok(game) -} - - -pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { - let mut game = game_get(tx, game_id)?; - game.clear_skill(account.id)?; - game_update(tx, &game)?; - - Ok(game) -} - -pub fn game_ready(tx: &mut Transaction, account: &Account, id: Uuid) -> Result { - let mut game = game_get(tx, id)?; - - game.player_ready(account.id)?; - - if game.skill_phase_finished() { - game = game.resolve_phase_start(); - } - - game_update(tx, &game)?; - - Ok(game) -} - -#[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, &"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, &"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, &"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, &"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 Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - // match event { - // Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Link), - // _ => panic!("not siphon"), - // }; - - // let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - // match event { - // Event::Recharge { red: _, blue: _, skill: _ } => (), - // _ => panic!("link result was not recharge"), - // } - - // // attack and receive link hit - // game.add_skill(y_player.id, y_construct.id, x_construct.id, Skill::Attack).unwrap(); - // game.player_ready(x_player.id).unwrap(); - // game.player_ready(y_player.id).unwrap(); - // game = game.resolve_phase_start(); - - // let Resolution { source: _, target, event, stages: _ } = game.resolved.pop().unwrap(); - // assert_eq!(target.id, y_construct.id); - // match event { - // Event::Damage { amount, skill: _, mitigation: _, colour: _} => - // assert_eq!(amount, x_construct.red_power().pct(Skill::Attack.multiplier()) >> 1), - // _ => panic!("not damage link"), - // }; - // } - - // #[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.resolved - .into_iter() - .filter(|r| { - let Resolution { source, target: _, event, stages: _ } = r; - match source.id == x_construct.id { - true => match event { - Event::Effect { effect, duration, skill: _, construct_effects: _ } => { - assert!(*effect == Effect::Stun); - assert!(*duration == 1); - true - }, - Event::AoeSkill { skill: _ } => false, - Event::Damage { amount: _, mitigation: _, colour: _, skill: _ } => false, - _ => panic!("ruin result not effect {:?}", event), - } - false => false, - } - }) - .count(); - - assert!(ruins == 2); - } - - #[test] - fn intercept_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Intercept); - - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Intercept).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Intercept).unwrap(); - game.add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap(); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - game = game.resolve_phase_start(); - - assert!(game.resolved.len() == 4); - while let Some(r) = game.resolved.pop() { - let Resolution { source , target, event: _, stages: _ } = r; - if [i_construct.id, j_construct.id].contains(&source.id) { - assert!(target.id == x_construct.id); - } - } - } - - #[test] - fn ko_pve_test() { - let mut game = create_2v2_test_game(); - - let i_player = game.players[0].clone(); - let x_player = game.players[1].clone(); - - let i_construct = i_player.constructs[0].clone(); - let j_construct = i_player.constructs[1].clone(); - let x_construct = x_player.constructs[0].clone(); - let y_construct = x_player.constructs[1].clone(); - - game.add_skill(i_player.id, i_construct.id, x_construct.id, Skill::Attack).unwrap() - .add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap() - .add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).unwrap() - .add_skill(x_player.id, y_construct.id, i_construct.id, Skill::Attack).unwrap() - .player_ready(i_player.id).unwrap() - .player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!([Phase::Skill, Phase::Finished].contains(&game.phase)); - - // kill a construct - game.player_by_id(i_player.id).unwrap().construct_by_id(i_construct.id).unwrap().green_life.reduce(u64::max_value()); - - assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); - assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); - - // add some more skills - game.add_skill(i_player.id, j_construct.id, x_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, x_construct.id, j_construct.id, Skill::Attack).unwrap(); - game.add_skill(x_player.id, y_construct.id, j_construct.id, Skill::Attack).unwrap(); - assert!(game.add_skill(x_player.id, x_construct.id, i_construct.id, Skill::Attack).is_err()); - - game.player_ready(i_player.id).unwrap(); - game.player_ready(x_player.id).unwrap(); - - assert!(game.skill_phase_finished()); - game = game.resolve_phase_start(); - - assert!(game.player_by_id(i_player.id).unwrap().skills_required() == 1); - assert!(game.player_by_id(x_player.id).unwrap().skills_required() == 2); - return; - } - - #[test] - fn tick_removal_test() { - let mut game = create_test_game(); - - let x_player = game.players[0].clone(); - let y_player = game.players[1].clone(); - - let x_construct = x_player.constructs[0].clone(); - let y_construct = y_player.constructs[0].clone(); - - // make the purify construct super fast so it beats out decay - game.construct_by_id(y_construct.id).unwrap().speed.force(10000000); - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Decay); - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Decay).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.construct_by_id(x_construct.id).unwrap().learn_mut(Skill::Siphon); - while game.construct_by_id(x_construct.id).unwrap().skill_on_cd(Skill::Siphon).is_some() { - game.construct_by_id(x_construct.id).unwrap().reduce_cooldowns(); - } - - game.construct_by_id(y_construct.id).unwrap().learn_mut(Skill::Purify); - while game.construct_by_id(y_construct.id).unwrap().skill_on_cd(Skill::Purify).is_some() { - game.construct_by_id(y_construct.id).unwrap().reduce_cooldowns(); - } - - // apply buff - game.add_skill(x_player.id, x_construct.id, y_construct.id, Skill::Decay).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - assert!(game.construct_by_id(y_construct.id).unwrap().affected(Effect::Decay)); - - let Resolution { source: _, target: _, event, stages: _ } = game.resolved.pop().unwrap(); - match event { - Event::Damage { amount: _, skill, mitigation: _, colour: _ } => assert_eq!(skill, Skill::DecayTick), - _ => panic!("not decay"), - }; - - game.resolved.clear(); - - // remove - game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { - match event { - Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => - panic!("{:?} damage event", event), - _ => (), - } - }; - - game.add_skill(y_player.id, x_construct.id, y_construct.id, Skill::Siphon).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - game.resolved.clear(); - - game.add_skill(y_player.id, y_construct.id, y_construct.id, Skill::Purify).unwrap(); - game.player_ready(x_player.id).unwrap(); - game.player_ready(y_player.id).unwrap(); - game = game.resolve_phase_start(); - - while let Some(Resolution { source: _, target: _, event, stages: _ }) = game.resolved.pop() { - match event { - Event::Damage { amount: _, skill: _, mitigation: _, colour: _ } => - panic!("{:#?} {:#?} damage event", game.resolved, event), - _ => (), - } - }; - - } - - #[test] - fn upkeep_test() { - let mut game = create_2v2_test_game(); - 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); - } -} diff --git a/server/src/http.rs b/server/src/http.rs index b64568a9..9c244317 100644 --- a/server/src/http.rs +++ b/server/src/http.rs @@ -14,9 +14,9 @@ use persistent::{Read, Write}; use router::Router; use mount::{Mount}; use serde::{Serialize, Deserialize}; -use lettre::{SendableEmail, SmtpClient, SmtpTransport, Transport}; +use lettre::{SmtpTransport}; -use acp; +// use acp; use account; use mail; use mail::Mail; @@ -517,7 +517,7 @@ pub fn start(pool: PgPool, mailer: SmtpTransport) { mounts.mount("/api/account/", account_mount()); mounts.mount("/api/payments/", payment_mount()); - mounts.mount("/api/acp/", acp::acp_mount()); + // mounts.mount("/api/acp/", acp::acp_mount()); let mut chain = Chain::new(mounts); chain.link(Read::::both(State { pool })); diff --git a/server/src/instance.rs b/server/src/instance.rs deleted file mode 100644 index 912b89c6..00000000 --- a/server/src/instance.rs +++ /dev/null @@ -1,959 +0,0 @@ -use std::fs::File; -use std::collections::{HashMap}; - -use uuid::Uuid; - -use serde_cbor::{from_slice, to_vec}; - -use postgres::transaction::Transaction; - -use failure::Error; -use failure::err_msg; - -// timekeeping -use chrono::prelude::*; -use chrono::Duration; - -use account::Account; -use vbox; - -use player::{Player, Score, player_create}; - -use mob::{bot_player, instance_mobs}; -use game::{Game, Phase, game_get, game_write, game_update}; -use item::{Item}; -use rpc::{RpcMessage}; -use img; - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -enum InstancePhase { - Lobby, - InProgress, - Finished, -} - -pub type ChatState = HashMap; - -#[derive(Debug,Clone,Serialize,Deserialize)] -struct Round { - game_id: Option, - finished: bool, -} - -impl Round { - fn new() -> Round { - Round { game_id: None, finished: false } - } -} - -#[derive(Debug,Clone,Copy,Serialize,Deserialize)] -pub enum TimeControl { - Standard, - Slow, - Practice, -} - -impl TimeControl { - fn vbox_time_seconds(&self) -> i64 { - match self { - TimeControl::Standard => 180, - TimeControl::Slow => 240, - TimeControl::Practice => panic!("practice vbox seconds called"), - } - } - - fn game_time_seconds(&self) -> i64 { - match self { - TimeControl::Standard => 60, - TimeControl::Slow => 120, - TimeControl::Practice => panic!("practice game seconds called"), - } - } - - pub fn vbox_phase_end(&self) -> Option> { - match self { - TimeControl::Practice => None, - _ => Some(Utc::now() - .checked_add_signed(Duration::seconds(self.vbox_time_seconds())) - .expect("could not set vbox phase end")), - } - } - - pub fn lobby_timeout(&self) -> DateTime { - Utc::now() - .checked_add_signed(Duration::seconds(15)) - .expect("could not set phase end") - } - - pub fn game_phase_end(&self, resolution_time_ms: i64) -> Option> { - match self { - TimeControl::Practice => None, - _ => Some(Utc::now() - .checked_add_signed(Duration::milliseconds(self.game_time_seconds() * 1000 + resolution_time_ms)) - .expect("could not set game phase end")), - } - } -} - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct Instance { - pub id: Uuid, - pub name: String, - - players: Vec, - rounds: Vec, - - max_players: usize, - time_control: TimeControl, - - phase: InstancePhase, - phase_end: Option>, - phase_start: DateTime, - - winner: Option, -} - -impl Instance { - fn new() -> Instance { - Instance { - id: Uuid::new_v4(), - players: vec![], - rounds: vec![], - phase: InstancePhase::Lobby, - max_players: 2, - name: String::new(), - time_control: TimeControl::Standard, - phase_start: Utc::now(), - phase_end: Some(TimeControl::Standard.lobby_timeout()), - winner: None, - } - } - - pub fn redact(mut self, account: Uuid) -> Instance { - self.players = self.players.into_iter() - .map(|p| p.redact(account)) - .collect(); - - self - } - - fn phase_timed_out(&self) -> bool { - match self.phase_end { - Some(t) => Utc::now().signed_duration_since(t).num_milliseconds() > 0, - None => false, - } - } - - fn timed_out_players(&self) -> Vec { - self.players - .iter() - .filter(|p| !p.ready) - .map(|p| p.id) - .collect::>() - } - - pub fn upkeep(mut self) -> (Instance, Option) { - // time out lobbies that have been open too long - if self.phase == InstancePhase::Lobby && self.phase_timed_out() { - self.finish(); - return (self, None); - } - - if self.phase != InstancePhase::InProgress { - return (self, None); - } - - if !self.phase_timed_out() { - return (self, None); - } - - let new_game = self - .timed_out_players() - .iter() - .filter_map(|p| self.player_ready(*p).unwrap()) - .collect::>() - .into_iter() - .next(); - - (self, new_game) - } - - fn set_name(mut self, name: String) -> Result { - if name.len() == 0 { - return Err(err_msg("name must have a length")); - } - - self.name = name; - Ok(self) - } - - fn set_time_control(mut self, tc: TimeControl) -> Instance { - self.time_control = tc; - self - } - - fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { - if self.players.len() >= self.max_players { - return Err(err_msg("game full")) - } - - match self.players.iter().find(|p| p.id == player.id) { - Some(_p) => return Err(err_msg("already joined")), - None => (), - }; - - self.players.push(player); - Ok(self) - } - - fn player_ready(&mut self, player_id: Uuid) -> Result, Error> { - if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) { - return Err(err_msg("instance not in start or vbox phase")); - } - - // LOBBY CHECKS - if self.phase == InstancePhase::Lobby { - let i = self.players - .iter_mut() - .position(|p| p.id == player_id) - .ok_or(err_msg("player_id not found"))?; - - let v = !self.players[i].ready; - self.players[i].set_ready(v); - - match self.can_start() { - true => { - self.start(); - return Ok(None); - } - false => return Ok(None), - }; - } - - // GAME PHASE READY - let i = self.players - .iter_mut() - .position(|p| p.id == player_id) - .ok_or(err_msg("player_id not found"))?; - - let v = !self.players[i].ready; - self.players[i].set_ready(v); - - // start the game even if afk noobs have no skills - if !self.phase_timed_out() && self.players[i].constructs.iter().all(|c| c.skills.len() == 0) { - return Err(err_msg("your constructs have no skills")); - } - - // create a game object if both players are ready - // this should only happen once - - let all_ready = self.round_ready_check(); - - if !all_ready { - return Ok(None); - } - - let game = self.create_round_game(); - - let current_round = self.rounds - .last_mut() - .expect("instance does not have any rounds"); - - current_round.game_id = Some(game.id); - - return Ok(Some(game)); - - } - - fn round_ready_check(&mut self) -> bool { - self.players - .iter() - .all(|p| p.ready) - } - - // maybe just embed the games in the instance - // but seems hella inefficient - fn create_round_game(&mut self) -> Game { - let current_round = self.rounds - .last_mut() - .expect("instance does not have any rounds"); - - - let mut game = Game::new(); - current_round.game_id = Some(game.id); - - // disable upkeep until players finish their game - self.phase_end = None; - - game - .set_player_num(2) - .set_player_constructs(3) - .set_time_control(self.time_control) - .set_instance(self.id); - - for player in self.players.clone().into_iter() { - game.player_add(player).unwrap(); - } - - assert!(game.can_start()); - return game.start(); - } - - fn can_start(&self) -> bool { - self.players.len() == self.max_players && self.all_ready() - } - - fn start(&mut self) -> &mut Instance { - // self.players.sort_unstable_by_key(|p| p.id); - self.next_round() - } - - fn next_round(&mut self) -> &mut Instance { - if self.finish_condition() { - return self.finish(); - } - - self.phase = InstancePhase::InProgress; - self.phase_start = Utc::now(); - self.phase_end = self.time_control.vbox_phase_end(); - - let bits = match self.rounds.len() > 0 { - true => 30, - false => 0, - }; - - self.players.iter_mut().for_each(|p| { - p.vbox.balance_add(bits); - p.set_ready(false); - p.vbox.fill(); - }); - - self.rounds.push(Round::new()); - self.bot_round_actions(); - - self - } - - fn finish_condition(&mut self) -> bool { - self.players.iter().any(|p| p.score == Score::Win) - } - - pub fn finish(&mut self) -> &mut Instance { - self.phase = InstancePhase::Finished; - - for player in self.players.iter() { - if player.score == Score::Win { - self.winner = Some(player.id); - } - } - - self - } - - fn finished(&self) -> bool { - self.phase == InstancePhase::Finished - } - - fn bot_round_actions(&mut self) -> &mut Instance { - for bot in self.players.iter_mut().filter(|p| p.bot) { - bot.vbox.fill(); - bot.autobuy(); - } - - let games = self.players - .clone() - .iter() - .filter(|b| b.bot) - .filter_map(|b| self.player_ready(b.id).unwrap()) - .collect::>(); - - for game in games { - if game.finished() { - self.game_finished(&game).unwrap(); - } else { - info!("{:?} unfishededes", game); - } - } - - self - } - - fn current_game_id(&self) -> Option { - if self.phase != InstancePhase::InProgress { - return None; - } - - let current_round = self.rounds - .last() - .expect("instance does not have any rounds"); - - if current_round.finished || current_round.game_id.is_none() { - return None; - } - - return current_round.game_id; - } - - fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { - { - let current_round = self.rounds - .iter_mut() - .filter(|r| r.game_id.is_some()) - .find(|r| r.game_id.unwrap() == game.id); - - match current_round { - Some(c) => c.finished = true, - None => return Err(err_msg("instance does not have a round for this game")), - }; - } - - // if you don't win, you lose - // ties can happen if both players agree to a draw - // or ticks fire and knock everybody out - if let Some(winner) = game.winner() { - let winner = self.players.iter_mut() - .find(|p| p.id == winner.id) - .unwrap(); - winner.score = winner.score.add_win(&Score::Zero); - }; - - if self.all_games_finished() { - self.next_round(); - } - - Ok(self) - } - - fn all_ready(&self) -> bool { - self.players.iter().all(|p| p.ready) - } - - fn all_games_finished(&self) -> bool { - match self.rounds.last() { - Some(r) => r.finished, - None => true, - } - } - - // PLAYER ACTIONS - fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> { - self.players - .iter_mut() - .find(|p| p.id == account) - .ok_or(err_msg("account not in instance")) - } - - fn account_opponent(&mut self, account: Uuid) -> Result<&mut Player, Error> { - self.players - .iter_mut() - .find(|p| p.id != account) - .ok_or(err_msg("opponent not in instance")) - } - - pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> { - if self.players.iter().find(|p| p.id == account).is_none() { - return Err(err_msg("player not in this instance")); - } - - if self.phase == InstancePhase::Lobby { - return Err(err_msg("game not yet started")); - } - - if self.current_game_id().is_some() { - return Err(err_msg("you cannot perform vbox actions while in a game")); - } - - Ok(()) - } - - pub fn vbox_refill(mut self, account: Uuid) -> Result { - self.vbox_action_allowed(account)?; - self.account_player(account)? - .vbox_refill()?; - Ok(self) - } - - pub fn vbox_buy(mut self, account: Uuid, group: vbox::ItemType, index: String, construct_id: Option) -> Result { - self.vbox_action_allowed(account)?; - self.account_player(account)? - .vbox_buy(group, index, construct_id)?; - Ok(self) - } - - pub fn vbox_combine(mut self, account: Uuid, inv_indices: Vec, vbox_indices: vbox::VboxIndices) -> Result { - self.vbox_action_allowed(account)?; - self.account_player(account)? - .vbox_combine(inv_indices, vbox_indices)?; - Ok(self) - } - - pub fn vbox_refund(mut self, account: Uuid, index: String) -> Result { - self.vbox_action_allowed(account)?; - self.account_player(account)? - .vbox_refund(index)?; - Ok(self) - } - - pub fn vbox_apply(mut self, account: Uuid, index: String, construct_id: Uuid) -> Result { - self.vbox_action_allowed(account)?; - self.account_player(account)? - .vbox_equip(index, construct_id)?; - Ok(self) - } - - pub fn vbox_unequip(mut self, account: Uuid, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result { - self.vbox_action_allowed(account)?; - self.account_player(account)? - .vbox_unequip(target, construct_id, target_construct_id)?; - Ok(self) - } -} - -pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result { - let instance_bytes = to_vec(&instance)?; - - let query = " - INSERT INTO instances (id, data, upkeep) - VALUES ($1, $2, $3) - RETURNING id; - "; - - let result = tx - .query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?; - - result.iter().next().ok_or(format_err!("no instances written"))?; - - return Ok(instance); -} - -pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result { - let instance_bytes = to_vec(&instance)?; - - let query = " - UPDATE instances - SET data = $1, finished = $2, upkeep = $3, updated_at = now() - WHERE id = $4 - RETURNING id, data; - "; - - let result = tx - .query(query, &[&instance_bytes, &instance.finished(), &instance.phase_end, &instance.id])?; - - result.iter().next().ok_or(err_msg("no instance row returned"))?; - - trace!("{:?} wrote instance", instance.id); - - if instance.finished() { - info!("finished id={:?}", instance.id); - - match instance_json_file_write(&instance) { - Ok(dest) => info!("wrote dest={:?}", dest), - Err(e) => error!("json write error={:?}", e), - }; - } - - return Ok(instance); -} - -fn instance_json_file_write(g: &Instance) -> Result { - let dest = format!("/var/lib/mnml/data/instances/{}.mnml.instance.json", g.id); - serde_json::to_writer(File::create(&dest)?, g)?; - Ok(dest) -} - -pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result { - let query = " - SELECT * - FROM instances - WHERE id = $1 - FOR UPDATE; - "; - - let result = tx - .query(query, &[&instance_id])?; - - let returned = match result.iter().next() { - Some(row) => row, - None => return Err(err_msg("instance not found")), - }; - - let instance_bytes: Vec = returned.get("data"); - let instance = from_slice::(&instance_bytes)?; - - return Ok(instance); -} - -pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { - let query = " - DELETE - FROM instances - WHERE id = $1; - "; - - let result = tx - .execute(query, &[&id])?; - - if result != 1 { - return Err(format_err!("unable to delete instance {:?}", id)); - } - - info!("instance deleted {:?}", id); - - return Ok(()); -} - -pub fn _instance_list(tx: &mut Transaction) -> Result, Error> { - let query = " - SELECT data, id - FROM instances - AND finished = false; - "; - - let result = tx - .query(query, &[])?; - - let mut list = vec![]; - - for row in result.into_iter() { - let bytes: Vec = row.get(0); - let id = row.get(1); - - match from_slice::(&bytes) { - Ok(i) => list.push(i), - Err(_e) => { - instance_delete(tx, id)?; - } - }; - } - - return Ok(list); -} - -pub fn instances_need_upkeep(tx: &mut Transaction) -> Result, Error> { - let query = " - SELECT data, id - FROM instances - WHERE finished = false - AND upkeep < now() - FOR UPDATE; - "; - - let result = tx - .query(query, &[])?; - - let mut list = vec![]; - - for row in result.into_iter() { - let bytes: Vec = row.get(0); - let id = row.get(1); - - match from_slice::(&bytes) { - Ok(i) => list.push(i), - Err(_e) => { - instance_delete(tx, id)?; - } - }; - } - - return Ok(list); -} - -// timed out instances with no time control -pub fn instances_idle(tx: &mut Transaction) -> Result, Error> { - let query = " - SELECT data, id - FROM instances - WHERE finished = false - AND updated_at < now() - interval '1 hour' - FOR UPDATE; - "; - - let result = tx - .query(query, &[])?; - - let mut list = vec![]; - - for row in result.into_iter() { - let bytes: Vec = row.get(0); - let id = row.get(1); - - match from_slice::(&bytes) { - Ok(i) => list.push(i), - Err(_e) => { - instance_delete(tx, id)?; - } - }; - } - - return Ok(list); -} - - -pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result { - let bot = bot_player(); - let bot_id = bot.id; - - // generate bot imgs for the client to see - for c in bot.constructs.iter() { - img::shapes_write(c.img)?; - } - - let mut instance = Instance::new() - .set_time_control(TimeControl::Practice) - .set_name(bot.name.clone())?; - - let player = Player::from_account(tx, account)?; - - instance.add_player(player.clone())?; - instance.add_player(bot)?; - - instance.player_ready(bot_id)?; - // skip faceoff - instance.player_ready(player.id)?; - - instance = instance_create(tx, instance)?; - player_create(tx, player, instance.id, account)?; - - Ok(instance) -} - -pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result { - let mut instance = Instance::new() - // TODO generate nice game names - .set_name("PVP".to_string())?; - - instance = instance_create(tx, instance)?; - - for account in [a, b].iter() { - let acc_p = Player::from_account(tx, &account)?; - let player = player_create(tx, acc_p, instance.id, account)?; - instance.add_player(player)?; - } - - instance_update(tx, instance) -} - -pub fn instance_abandon(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { - let mut instance = instance_get(tx, instance_id)?; - - if let Some(game_id) = instance.current_game_id() { - let mut game = game_get(tx, game_id)?; - game.player_by_id(account.id)?.forfeit(); - game = game.start(); // actually finishes it... - game_update(tx, &game)?; - } - - instance.account_player(account.id)?.set_lose(); - instance.account_opponent(account.id)?.set_win(); - instance.next_round(); - - Ok(RpcMessage::InstanceState(instance_update(tx, instance)?)) -} - -pub fn instance_ready(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { - let mut instance = instance_get(tx, instance_id)?; - let player_id = instance.account_player(account.id)?.id; - - if let Some(game) = instance.player_ready(player_id)? { - game_write(tx, &game)?; - - // ensures cleanup for warden etc is done - game_update(tx, &game)?; - - instance_update(tx, instance)?; - return Ok(RpcMessage::GameState(game)); - } - - Ok(RpcMessage::InstanceState(instance_update(tx, instance)?)) -} - -pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result { - let instance = instance_get(tx, instance_id)?; - - if let Some(game_id) = instance.current_game_id() { - let game = game_get(tx, game_id)?; - - // return the game until it's finished - if game.phase != Phase::Finished { - return Ok(RpcMessage::GameState(game)) - } - } - - Ok(RpcMessage::InstanceState(instance)) -} - -pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> { - let mut instance = instance_get(tx, instance_id)?; - instance.game_finished(game)?; - // info!("{:?}", instance_get(tx, instance_id)?); - - instance_update(tx, instance)?; - - Ok(()) -} - -pub fn bot_instance() -> Instance { - let mut instance = Instance::new(); - - let bot_player = bot_player(); - let bot = bot_player.id; - instance.add_player(bot_player).unwrap(); - - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); - - instance.add_player(player).expect("could not add player"); - instance.player_ready(player_account).unwrap(); - instance.player_ready(bot).unwrap(); - - return instance; -} - -pub fn demo() -> Result, Error> { - let bot = bot_player(); - - // generate bot imgs for the client to see - for c in bot.constructs.iter() { - img::shapes_write(c.img)?; - }; - - let bot2 = bot_player(); - - // generate bot imgs for the client to see - for c in bot2.constructs.iter() { - img::shapes_write(c.img)?; - }; - - - Ok(vec![bot, bot2]) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn instance_pve_test() { - let mut instance = Instance::new(); - - let bot = bot_player(); - let bot_one = bot.id; - instance.add_player(bot).unwrap(); - - let bot = bot_player(); - let bot_two = bot.id; - instance.add_player(bot).unwrap(); - - assert_eq!(instance.phase, InstancePhase::Lobby); - instance.player_ready(bot_one).unwrap(); - instance.player_ready(bot_two).unwrap(); - - assert_eq!(instance.phase, InstancePhase::Finished); - } - - #[test] - fn instance_bot_vbox_test() { - let _instance = Instance::new(); - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let _player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); - } - - #[test] - fn instance_start_test() { - let mut instance = Instance::new(); - - assert_eq!(instance.max_players, 2); - - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"a".to_string(), constructs); - let a_id = player.id; - - instance.add_player(player).expect("could not add player"); - assert!(!instance.can_start()); - - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"b".to_string(), constructs); - let b_id = player.id; - - instance.add_player(player).expect("could not add player"); - - assert_eq!(instance.phase, InstancePhase::Lobby); - instance.player_ready(a_id).expect("a ready"); - assert!(!instance.can_start()); - - instance.player_ready(b_id).expect("b ready"); - assert_eq!(instance.phase, InstancePhase::InProgress); - - assert!(!instance.can_start()); - - instance.players[0].autobuy(); - instance.players[1].autobuy(); - - instance.player_ready(a_id).expect("a ready"); - let game = instance.player_ready(b_id).expect("b ready"); - - assert!(game.is_some()); - } - - #[test] - fn instance_upkeep_test() { - let mut instance = Instance::new(); - - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"a".to_string(), constructs); - let a_id = player.id; - - instance.add_player(player).expect("could not add player"); - assert!(!instance.can_start()); - - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"b".to_string(), constructs); - let b_id = player.id; - instance.add_player(player).expect("could not add player"); - - instance.players[0].autobuy(); - - instance.player_ready(a_id).expect("a ready"); - instance.player_ready(b_id).expect("b ready"); - - instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::seconds(500)).unwrap()); - - let (mut instance, new_games) = instance.upkeep(); - - assert!(new_games.is_some()); - - let game = new_games.unwrap(); - assert!(game.finished()); - - instance.game_finished(&game).unwrap(); - - assert_eq!(instance.rounds.len(), 2); - assert!(instance.players.iter().all(|p| !p.ready)); - - // info!("{:#?}", instance); - } - - #[test] - fn instance_upkeep_idle_lobby_test() { - let mut instance = Instance::new(); - - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let player = Player::new(player_account, &"a".to_string(), constructs); - let _a_id = player.id; - - instance.add_player(player).expect("could not add player"); - assert!(!instance.can_start()); - - instance.phase_end = Some(Utc::now().checked_sub_signed(Duration::minutes(61)).unwrap()); - let (instance, _new_games) = instance.upkeep(); - - assert!(instance.finished()); - } -} diff --git a/server/src/item.rs b/server/src/item.rs deleted file mode 100644 index 0b50dca4..00000000 --- a/server/src/item.rs +++ /dev/null @@ -1,1607 +0,0 @@ -use skill::{Skill}; -use spec::{Spec, SpecValues}; -use construct::{Colours}; -use effect::{Colour, Cooldown}; - -#[derive(Debug,Copy,Clone,Serialize,Deserialize,PartialEq,PartialOrd,Ord,Eq)] -pub enum Item { - // colours - Blue, - Green, - Red, - - // base skills - Attack, - Block, - Stun, - Buff, - Debuff, - - // specs - // Base - Power, - Life, - Speed, - - // Lifes Upgrades - LifeGG, - LifeRR, - LifeBB, - LifeRG, - LifeGB, - LifeRB, - LifeGGPlus, - LifeRRPlus, - LifeBBPlus, - LifeRGPlus, - LifeGBPlus, - LifeRBPlus, - LifeGGPlusPlus, - LifeRRPlusPlus, - LifeBBPlusPlus, - LifeRGPlusPlus, - LifeGBPlusPlus, - LifeRBPlusPlus, - - // Power Upgrades - PowerGG, - PowerRR, - PowerBB, - PowerRG, - PowerGB, - PowerRB, - PowerGGPlus, - PowerRRPlus, - PowerBBPlus, - PowerRGPlus, - PowerGBPlus, - PowerRBPlus, - PowerGGPlusPlus, - PowerRRPlusPlus, - PowerBBPlusPlus, - PowerRGPlusPlus, - PowerGBPlusPlus, - PowerRBPlusPlus, - - // Speed Upgrades - SpeedGG, - SpeedRR, - SpeedBB, - SpeedRG, - SpeedGB, - SpeedRB, - SpeedGGPlus, - SpeedRRPlus, - SpeedBBPlus, - SpeedRGPlus, - SpeedGBPlus, - SpeedRBPlus, - SpeedGGPlusPlus, - SpeedRRPlusPlus, - SpeedBBPlusPlus, - SpeedRGPlusPlus, - SpeedGBPlusPlus, - SpeedRBPlusPlus, - - Amplify, - #[serde(rename = "Amplify+")] - AmplifyPlus, - #[serde(rename = "Amplify++")] - AmplifyPlusPlus, - - Absorb, - #[serde(rename = "Absorb+")] - AbsorbPlus, - #[serde(rename = "Absorb++")] - AbsorbPlusPlus, - - Banish, - #[serde(rename = "Banish+")] - BanishPlus, - #[serde(rename = "Banish++")] - BanishPlusPlus, - - Bash, - #[serde(rename = "Bash+")] - BashPlus, - #[serde(rename = "Bash++")] - BashPlusPlus, - - Blast, - #[serde(rename = "Blast+")] - BlastPlus, - #[serde(rename = "Blast++")] - BlastPlusPlus, - - Chaos, - #[serde(rename = "Chaos+")] - ChaosPlus, - #[serde(rename = "Chaos++")] - ChaosPlusPlus, - - Sustain, - #[serde(rename = "Sustain+")] - SustainPlus, - #[serde(rename = "Sustain++")] - SustainPlusPlus, - - Electrify, - #[serde(rename = "Electrify+")] - ElectrifyPlus, - #[serde(rename = "Electrify++")] - ElectrifyPlusPlus, - - Curse, - #[serde(rename = "Curse+")] - CursePlus, - #[serde(rename = "Curse++")] - CursePlusPlus, - - Decay, - #[serde(rename = "Decay+")] - DecayPlus, - #[serde(rename = "Decay++")] - DecayPlusPlus, - - Haste, - #[serde(rename = "Haste+")] - HastePlus, - #[serde(rename = "Haste++")] - HastePlusPlus, - - Heal, - #[serde(rename = "Heal+")] - HealPlus, - #[serde(rename = "Heal++")] - HealPlusPlus, - - Hybrid, - #[serde(rename = "Hybrid+")] - HybridPlus, - #[serde(rename = "Hybrid++")] - HybridPlusPlus, - - Invert, - #[serde(rename = "Invert+")] - InvertPlus, - #[serde(rename = "Invert++")] - InvertPlusPlus, - - Counter, - #[serde(rename = "Counter+")] - CounterPlus, - #[serde(rename = "Counter++")] - CounterPlusPlus, - - Purge, - #[serde(rename = "Purge+")] - PurgePlus, - #[serde(rename = "Purge++")] - PurgePlusPlus, - - Purify, - #[serde(rename = "Purify+")] - PurifyPlus, - #[serde(rename = "Purify++")] - PurifyPlusPlus, - - Reflect, - #[serde(rename = "Reflect+")] - ReflectPlus, - #[serde(rename = "Reflect++")] - ReflectPlusPlus, - - Recharge, - #[serde(rename = "Recharge+")] - RechargePlus, - #[serde(rename = "Recharge++")] - RechargePlusPlus, - - Ruin, - #[serde(rename = "Ruin+")] - RuinPlus, - #[serde(rename = "Ruin++")] - RuinPlusPlus, - - Link, - #[serde(rename = "Link+")] - LinkPlus, - #[serde(rename = "Link++")] - LinkPlusPlus, - - Silence, - #[serde(rename = "Silence+")] - SilencePlus, - #[serde(rename = "Silence++")] - SilencePlusPlus, - - Slay, - #[serde(rename = "Slay+")] - SlayPlus, - #[serde(rename = "Slay++")] - SlayPlusPlus, - - Sleep, - #[serde(rename = "Sleep+")] - SleepPlus, - #[serde(rename = "Sleep++")] - SleepPlusPlus, - - Restrict, - #[serde(rename = "Restrict+")] - RestrictPlus, - #[serde(rename = "Restrict++")] - RestrictPlusPlus, - - Strike, - #[serde(rename = "Strike+")] - StrikePlus, - #[serde(rename = "Strike++")] - StrikePlusPlus, - - Siphon, - #[serde(rename = "Siphon+")] - SiphonPlus, - #[serde(rename = "Siphon++")] - SiphonPlusPlus, - - Intercept, - #[serde(rename = "Intercept+")] - InterceptPlus, - #[serde(rename = "Intercept++")] - InterceptPlusPlus, - - Break, - #[serde(rename = "Break+")] - BreakPlus, - #[serde(rename = "Break++")] - BreakPlusPlus, - - Triage, - #[serde(rename = "Triage+")] - TriagePlus, - #[serde(rename = "Triage++")] - TriagePlusPlus, -} - -pub enum ItemEffect { - Skill, - Spec, -} - -impl Item { - pub fn colours(&self, count: &mut Colours) { - let combos = get_combos(); - let combo = combos.iter().find(|c| c.item == *self); - match combo { - Some(c) => c.components.iter().for_each(|unit| match unit { - Item::Red => count.red += 1, - Item::Blue => count.blue += 1, - Item::Green => count.green += 1, - _ => { - let mut combo_count = Colours::new(); - unit.colours(&mut combo_count); - count.red += combo_count.red; - count.blue += combo_count.blue; - count.green += combo_count.green; - } - }), - None => (), - } - } - - pub fn components(&self) -> Vec { - let combos = get_combos(); - let combo = combos.iter().find(|c| c.item == *self); - - match combo { - Some(c) => c.components.iter().flat_map(|c| c.components()).collect::>(), - None => vec![*self], - } - } - - pub fn cost(&self) -> usize { - match self { - Item::Red => 1, - Item::Green => 1, - Item::Blue => 1, - - Item::Attack => 2, - Item::Block => 2, - Item::Buff => 2, - Item::Debuff => 2, - Item::Stun => 2, - - Item::Power => 3, - Item::Life => 3, - Item::Speed => 3, - - _ => { - let combos = get_combos(); - let combo = combos.iter().find(|c| c.item == *self) - .unwrap_or_else(|| panic!("unable to find components for {:?}", self)); - return combo.components.iter().fold(0, |acc, c| { - match c { - Item::Attack | - Item::Block | - Item::Buff | - Item::Debuff | - Item::Stun => acc, - Item::Power | - Item::Life | - Item::Speed => acc + 1, - _ => acc + c.cost(), - } - }); - }, - } - } - - pub fn base_speed(&self) -> u64 { - match self { - Item::Attack => 1, - Item::Stun => 2, - Item::Block => 3, - Item::Buff | - Item::Debuff => 4, - Item::Blue => 1, - Item::Green => 2, - Item::Red => 3, - _ => 0, - } - } - - pub fn speed(&self) -> u64 { - match self { - Item::Attack | - Item::Stun | - Item::Block | - Item::Buff | - Item::Debuff => 24 + self.base_speed(), - _ => { - let combos = get_combos(); - let combo = combos.iter().find(|c| c.item == *self) - .unwrap_or_else(|| panic!("unable to find components for {:?}", self)); - - let mut colour_speed = 0; - let mut skill_speed = 0; - let mut component_speed = 0; - - combo.components.iter().for_each(|unit| { - colour_speed += match unit { - Item::Red | - Item::Green | - Item::Blue => unit.base_speed(), - _ => 0, - }; - skill_speed += match unit { - Item::Attack | - Item::Stun | - Item::Block | - Item::Buff | - Item::Debuff => unit.base_speed(), - _ => 0, - }; - if colour_speed == 0 && skill_speed == 0 { - component_speed = unit.speed(); - } - }); - if component_speed > 0 { return component_speed }; - return 24 + colour_speed * skill_speed - } - } - - } - - pub fn effect(&self) -> Option { - if let Some(_skill) = self.into_skill() { - return Some(ItemEffect::Skill); - } - if let Some(_spec) = self.into_spec() { - return Some(ItemEffect::Spec); - } - return None; - } - - pub fn into_skill(&self) -> Option { - match self { - Item::Absorb => Some(Skill::Absorb), - Item::AbsorbPlus => Some(Skill::AbsorbPlus), - Item::AbsorbPlusPlus => Some(Skill::AbsorbPlusPlus), - Item::Amplify => Some(Skill::Amplify), - Item::AmplifyPlus => Some(Skill::AmplifyPlus), - Item::AmplifyPlusPlus => Some(Skill::AmplifyPlusPlus), - Item::Attack => Some(Skill::Attack), - Item::Banish => Some(Skill::Banish), - Item::BanishPlus => Some(Skill::BanishPlus), - Item::BanishPlusPlus => Some(Skill::BanishPlusPlus), - Item::Bash => Some(Skill::Bash), - Item::BashPlus => Some(Skill::BashPlus), - Item::BashPlusPlus => Some(Skill::BashPlusPlus), - Item::Blast => Some(Skill::Blast), - Item::BlastPlus => Some(Skill::BlastPlus), - Item::BlastPlusPlus => Some(Skill::BlastPlusPlus), - Item::Block => Some(Skill::Block), - Item::Buff => Some(Skill::Buff), - Item::Chaos => Some(Skill::Chaos), - Item::ChaosPlus => Some(Skill::ChaosPlus), - Item::ChaosPlusPlus => Some(Skill::ChaosPlusPlus), - Item::Counter => Some(Skill::Counter), - Item::CounterPlus => Some(Skill::CounterPlus), - Item::CounterPlusPlus => Some(Skill::CounterPlusPlus), - Item::Curse => Some(Skill::Curse), - Item::CursePlus => Some(Skill::CursePlus), - Item::CursePlusPlus => Some(Skill::CursePlusPlus), - Item::Debuff => Some(Skill::Debuff), - Item::Decay => Some(Skill::Decay), - Item::DecayPlus => Some(Skill::DecayPlus), - Item::DecayPlusPlus => Some(Skill::DecayPlusPlus), - Item::Electrify => Some(Skill::Electrify), - Item::ElectrifyPlus => Some(Skill::ElectrifyPlus), - Item::ElectrifyPlusPlus => Some(Skill::ElectrifyPlusPlus), - Item::Haste => Some(Skill::Haste), - Item::HastePlus => Some(Skill::HastePlus), - Item::HastePlusPlus => Some(Skill::HastePlusPlus), - Item::Heal => Some(Skill::Heal), - Item::HealPlus => Some(Skill::HealPlus), - Item::HealPlusPlus => Some(Skill::HealPlusPlus), - Item::Hybrid => Some(Skill::Hybrid), - Item::HybridPlus => Some(Skill::HybridPlus), - Item::HybridPlusPlus => Some(Skill::HybridPlusPlus), - Item::Intercept => Some(Skill::Intercept), - Item::InterceptPlus => Some(Skill::InterceptPlus), - Item::InterceptPlusPlus => Some(Skill::InterceptPlusPlus), - Item::Invert => Some(Skill::Invert), - Item::InvertPlus => Some(Skill::InvertPlus), - Item::InvertPlusPlus => Some(Skill::InvertPlusPlus), - Item::Purge => Some(Skill::Purge), - Item::PurgePlus => Some(Skill::PurgePlus), - Item::PurgePlusPlus => Some(Skill::PurgePlusPlus), - Item::Purify => Some(Skill::Purify), - Item::PurifyPlus => Some(Skill::PurifyPlus), - Item::PurifyPlusPlus => Some(Skill::PurifyPlusPlus), - Item::Recharge => Some(Skill::Recharge), - Item::RechargePlus => Some(Skill::RechargePlus), - Item::RechargePlusPlus => Some(Skill::RechargePlusPlus), - Item::Reflect => Some(Skill::Reflect), - Item::ReflectPlus => Some(Skill::ReflectPlus), - Item::ReflectPlusPlus => Some(Skill::ReflectPlusPlus), - Item::Restrict => Some(Skill::Restrict), - Item::RestrictPlus => Some(Skill::RestrictPlus), - Item::RestrictPlusPlus => Some(Skill::RestrictPlusPlus), - Item::Ruin => Some(Skill::Ruin), - Item::RuinPlus => Some(Skill::RuinPlus), - Item::RuinPlusPlus => Some(Skill::RuinPlusPlus), - Item::Link => Some(Skill::Link), - Item::LinkPlus => Some(Skill::LinkPlus), - Item::LinkPlusPlus => Some(Skill::LinkPlusPlus), - Item::Silence => Some(Skill::Silence), - Item::SilencePlus => Some(Skill::SilencePlus), - Item::SilencePlusPlus => Some(Skill::SilencePlusPlus), - Item::Siphon => Some(Skill::Siphon), - Item::SiphonPlus => Some(Skill::SiphonPlus), - Item::SiphonPlusPlus => Some(Skill::SiphonPlusPlus), - Item::Slay => Some(Skill::Slay), - Item::SlayPlus => Some(Skill::SlayPlus), - Item::SlayPlusPlus => Some(Skill::SlayPlusPlus), - Item::Sleep => Some(Skill::Sleep), - Item::SleepPlus => Some(Skill::SleepPlus), - Item::SleepPlusPlus => Some(Skill::SleepPlusPlus), - Item::Strike => Some(Skill::Strike), - Item::StrikePlus => Some(Skill::StrikePlus), - Item::StrikePlusPlus => Some(Skill::StrikePlusPlus), - Item::Stun => Some(Skill::Stun), - Item::Sustain => Some(Skill::Sustain), - Item::SustainPlus => Some(Skill::SustainPlus), - Item::SustainPlusPlus => Some(Skill::SustainPlusPlus), - Item::Break => Some(Skill::Break), - Item::BreakPlus => Some(Skill::BreakPlus), - Item::BreakPlusPlus => Some(Skill::BreakPlusPlus), - Item::Triage => Some(Skill::Triage), - Item::TriagePlus => Some(Skill::TriagePlus), - Item::TriagePlusPlus => Some(Skill::TriagePlusPlus), - _ => None, - } - } - - pub fn into_spec(&self) -> Option { - match *self { - Item::Speed => Some(Spec::Speed), - Item::SpeedRR => Some(Spec::SpeedRR), - Item::SpeedBB => Some(Spec::SpeedBB), - Item::SpeedGG => Some(Spec::SpeedGG), - Item::SpeedRG => Some(Spec::SpeedRG), - Item::SpeedGB => Some(Spec::SpeedGB), - Item::SpeedRB => Some(Spec::SpeedRB), - - Item::SpeedRRPlus => Some(Spec::SpeedRRPlus), - Item::SpeedBBPlus => Some(Spec::SpeedBBPlus), - Item::SpeedGGPlus => Some(Spec::SpeedGGPlus), - Item::SpeedRGPlus => Some(Spec::SpeedRGPlus), - Item::SpeedGBPlus => Some(Spec::SpeedGBPlus), - Item::SpeedRBPlus => Some(Spec::SpeedRBPlus), - - Item::SpeedRRPlusPlus => Some(Spec::SpeedRRPlusPlus), - Item::SpeedBBPlusPlus => Some(Spec::SpeedBBPlusPlus), - Item::SpeedGGPlusPlus => Some(Spec::SpeedGGPlusPlus), - Item::SpeedRGPlusPlus => Some(Spec::SpeedRGPlusPlus), - Item::SpeedGBPlusPlus => Some(Spec::SpeedGBPlusPlus), - Item::SpeedRBPlusPlus => Some(Spec::SpeedRBPlusPlus), - - Item::Power => Some(Spec::Power), - Item::PowerRR => Some(Spec::PowerRR), - Item::PowerBB => Some(Spec::PowerBB), - Item::PowerGG => Some(Spec::PowerGG), - Item::PowerRG => Some(Spec::PowerRG), - Item::PowerGB => Some(Spec::PowerGB), - Item::PowerRB => Some(Spec::PowerRB), - Item::PowerRRPlus => Some(Spec::PowerRRPlus), - Item::PowerBBPlus => Some(Spec::PowerBBPlus), - Item::PowerGGPlus => Some(Spec::PowerGGPlus), - Item::PowerRGPlus => Some(Spec::PowerRGPlus), - Item::PowerGBPlus => Some(Spec::PowerGBPlus), - Item::PowerRBPlus => Some(Spec::PowerRBPlus), - Item::PowerRRPlusPlus => Some(Spec::PowerRRPlusPlus), - Item::PowerBBPlusPlus => Some(Spec::PowerBBPlusPlus), - Item::PowerGGPlusPlus => Some(Spec::PowerGGPlusPlus), - Item::PowerRGPlusPlus => Some(Spec::PowerRGPlusPlus), - Item::PowerGBPlusPlus => Some(Spec::PowerGBPlusPlus), - Item::PowerRBPlusPlus => Some(Spec::PowerRBPlusPlus), - - Item::Life => Some(Spec::Life), - Item::LifeRG => Some(Spec::LifeRG), - Item::LifeGB => Some(Spec::LifeGB), - Item::LifeRB => Some(Spec::LifeRB), - Item::LifeGG => Some(Spec::LifeGG), - Item::LifeRR => Some(Spec::LifeRR), - Item::LifeBB => Some(Spec::LifeBB), - Item::LifeRGPlus => Some(Spec::LifeRGPlus), - Item::LifeGBPlus => Some(Spec::LifeGBPlus), - Item::LifeRBPlus => Some(Spec::LifeRBPlus), - Item::LifeGGPlus => Some(Spec::LifeGGPlus), - Item::LifeRRPlus => Some(Spec::LifeRRPlus), - Item::LifeBBPlus => Some(Spec::LifeBBPlus), - Item::LifeRGPlusPlus => Some(Spec::LifeRGPlusPlus), - Item::LifeGBPlusPlus => Some(Spec::LifeGBPlusPlus), - Item::LifeRBPlusPlus => Some(Spec::LifeRBPlusPlus), - Item::LifeGGPlusPlus => Some(Spec::LifeGGPlusPlus), - Item::LifeRRPlusPlus => Some(Spec::LifeRRPlusPlus), - Item::LifeBBPlusPlus => Some(Spec::LifeBBPlusPlus), - - _ => None, - } - } - - pub fn into_colour(&self) -> Colour { - match *self { - Item::Red => Colour::Red, - Item::Green => Colour::Green, - Item::Blue => Colour::Blue, - _ => panic!("{:?} is not a colour", self), - } - } - - pub fn into_description(&self) -> String { - match self { - // colours - Item::Blue => format!("Combine two colours with a white base item to create a new combo. \n Slow speed, magical type. Deterrents and destruction."), - Item::Green => format!("Combine two colours with a white base item to create a new combo.\n Normal speed, healing type. Protection and trickery."), - Item::Red => format!("Combine two colours with a white base item to create a new combo. \n Fast speed, physical type. Chaos and momentum."), - - // base skills - Item::Attack => format!("Deal {:?}% RedPower as red damage.", - self.into_skill().unwrap().multiplier()), - Item::Block => format!("Reduce red damage and blue damage taken by {:?}%. Block lasts {:?}T", - 100 - self.into_skill().unwrap().effect()[0].get_multiplier(), - self.into_skill().unwrap().effect()[0].get_duration()), - - - Item::Stun => format!("Stun target construct for {:?}T.", - self.into_skill().unwrap().effect()[0].get_duration()), - Item::Buff => format!("Increase target construct RedPower BluePower SpeedStat by {:?}%. Buff lasts {:?}T", - self.into_skill().unwrap().effect()[0].get_multiplier() - 100, - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Debuff => format!("Slows the target reducing SpeedStat by {:?}%. Debuff lasts {:?}T", - 100 - self.into_skill().unwrap().effect()[0].get_multiplier(), - self.into_skill().unwrap().effect()[0].get_duration()), - // specs - // Base - Item::Power => format!("Increases all power stats by {:?}%. - Power determines the base damage and healing of your construct skills.", - self.into_spec().unwrap().values().base()), - Item::Life => format!("Increases construct GreenLife by {:?}. - When your construct reaches 0 GreenLife it is knocked out and cannot cast skills.", - self.into_spec().unwrap().values().base()), - Item::Speed => format!("Increases construct speed by {:?}%. - Speed SpeedStat determines the order in which skills resolve.", - self.into_spec().unwrap().values().base()), - - // Lifes Upgrades - Item::LifeGG | - Item::LifeGGPlus | - Item::LifeGGPlusPlus => format!("Increases construct GreenLife by {:?}. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - Item::LifeRR | - Item::LifeRRPlus | - Item::LifeRRPlusPlus => format!("Increases construct RedLife by {:?}. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - Item::LifeBB | - Item::LifeBBPlus | - Item::LifeBBPlusPlus => format!("Increases construct BlueLife by {:?}. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - Item::LifeRG | - Item::LifeRGPlus | - Item::LifeRGPlusPlus => format!("Increases construct RedLife and GreenLife by {:?}. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - Item::LifeGB | - Item::LifeGBPlus | - Item::LifeGBPlusPlus => format!("Increases construct GreenLife and BlueLife by {:?}. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - Item::LifeRB | - Item::LifeRBPlus | - Item::LifeRBPlusPlus => format!("Increases construct RedLife and BlueLife by {:?}. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - // Power Upgrades - Item::PowerRR | - Item::PowerRRPlus | - Item::PowerRRPlusPlus => format!("Increases construct RedPower by {:?}%. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - Item::PowerBB | - Item::PowerBBPlus | - Item::PowerBBPlusPlus => format!("Increases construct BluePower by {:?}%. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - Item::PowerGG | - Item::PowerGGPlus | - Item::PowerGGPlusPlus => format!("Increases construct GreenPower by {:?}%. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - Item::PowerRG | - Item::PowerRGPlus | - Item::PowerRGPlusPlus => format!("Increases construct GreenPower and RedPower by {:?}%. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - Item::PowerGB | - Item::PowerGBPlus | - Item::PowerGBPlusPlus => format!("Increases construct GreenPower and BluePower by {:?}%. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - Item::PowerRB | - Item::PowerRBPlus | - Item::PowerRBPlusPlus => format!("Increases construct RedPower and BluePower by {:?}%. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - // Speed Upgrades - Item::SpeedRR | - Item::SpeedBB | - Item::SpeedGG | - Item::SpeedRG | - Item::SpeedGB | - Item::SpeedRB | - Item::SpeedRRPlus | - Item::SpeedBBPlus | - Item::SpeedGGPlus | - Item::SpeedRGPlus | - Item::SpeedGBPlus | - Item::SpeedRBPlus | - Item::SpeedRRPlusPlus | - Item::SpeedBBPlusPlus | - Item::SpeedGGPlusPlus | - Item::SpeedRGPlusPlus | - Item::SpeedGBPlusPlus | - Item::SpeedRBPlusPlus => format!("Increases construct SpeedStat by {:?}%. - If your team meets total colour thresholds the spec provides additional bonuses.", - self.into_spec().unwrap().values().base()), - - // Skills <- need to move effect mulltipliers into skills - Item::Amplify| - Item::AmplifyPlus | - Item::AmplifyPlusPlus => format!("Increase RedPower BluePower by {:?}%. Lasts {:?}T.", - self.into_skill().unwrap().effect()[0].get_multiplier() - 100, - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Banish| - Item::BanishPlus | - Item::BanishPlusPlus => format!("Banish target for {:?}T. - Deal {:?}% target RedLife and BlueLife as red and blue damage respectively. - Banished constructs are immune to all skills and effects.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Blast| - Item::BlastPlus | - Item::BlastPlusPlus => format!("Deals {:?}% BluePower as blue damage.", self.into_skill().unwrap().multiplier()), - - Item::Chaos| - Item::ChaosPlus | - Item::ChaosPlusPlus => format!( - "Hits twice for red and blue damage. Damage {:?}% RedPower and BluePower. - Randomly deals 0 to 30% more damage.", - self.into_skill().unwrap().multiplier()), - - Item::Sustain| - Item::SustainPlus | - Item::SustainPlusPlus => format!( - "Construct cannot be KO'd while active and provides immunity to disables. Lasts {:?}T. - Recharges target RedLife based on {:?}% RedPower.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Electrify| - Item::ElectrifyPlus | - Item::ElectrifyPlusPlus => format!( - "Applies electrify for {:?}T. - If a construct with electrify takes direct damage they will apply an electrocute debuff to the caster. - Electrocute deals {:?}% BluePower as BlueDamage per turn for {:?}T.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), - self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration()), - - Item::Curse| - Item::CursePlus | - Item::CursePlusPlus => format!( - "Increases red and blue damage taken by {:?}%. Lasts {:?}T.", - self.into_skill().unwrap().effect()[0].get_multiplier() - 100, - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Decay| - Item::DecayPlus | - Item::DecayPlusPlus => format!( - "Reduces healing taken by {:?}% for {:?}T. - Deals blue damage {:?}% BluePower each turn for {:?}T.", - 100 - self.into_skill().unwrap().effect()[0].get_multiplier(), - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().effect()[1].get_skill().unwrap().multiplier(), - self.into_skill().unwrap().effect()[1].get_duration()), - - Item::Absorb| - Item::AbsorbPlus | - Item::AbsorbPlusPlus => format!( - "Gain Absorb for {:?}T. Taking damage replaces Absorb with Absorption. - Absorption increases RedPower and BluePower based on damage taken. - Absorption lasts {:?}T. Recharges BlueLife based on {:?}% BluePower.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().effect()[0].get_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Haste| - Item::HastePlus | - Item::HastePlusPlus => format!( - "Haste increases SpeedStat by {:?}%. - Red Attack based skills will strike again dealing {:?}% SpeedStat as red damage. - Lasts {:?}T", - self.into_skill().unwrap().effect()[0].get_multiplier() - 100, - Skill::HasteStrike.multiplier(), - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Heal| - Item::HealPlus | - Item::HealPlusPlus => format!("Heals target for {:?}% GreenPower.", self.into_skill().unwrap().multiplier()), - - Item::Hybrid| - Item::HybridPlus | - Item::HybridPlusPlus => format!( - "Hybrid increases GreenPower by {:?}%. - Blue based Attack skills will blast again dealing {:?}% GreenPower as blue damage. - Lasts {:?}T.", - self.into_skill().unwrap().effect()[0].get_multiplier() - 100, - Skill::HybridBlast.multiplier(), - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Invert| - Item::InvertPlus | - Item::InvertPlusPlus => format!( - "Reverse healing/recharge into damage and damage into healing/recharge. - Any excess red or blue damage is converted into shield recharge after healing. - Lasts {:?}T.", - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Counter| - Item::CounterPlus | - Item::CounterPlusPlus => format!( - "Applies counter for {:?}T. - Red damage taken during counter will trigger a counter attack. - Counter attack deals {:?}% RedPower as red damage.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier()), - - Item::Purge| - Item::PurgePlus | - Item::PurgePlusPlus => format!( - "Remove all effects from target construct. - Applies purge disabling target green skills for {:?}T.", - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Purify| - Item::PurifyPlus | - Item::PurifyPlusPlus => format!( - "Remove all effects and heals for {:?}% GreenPower per effect removed. - Applies Pure increasing healing taken by {:?}%.", - self.into_skill().unwrap().multiplier(), - self.into_skill().unwrap().effect()[0].get_multiplier() - 100), - - Item::Reflect| - Item::ReflectPlus | - Item::ReflectPlusPlus => format!( - "Reflect incoming blue skills to source. Lasts {:?}T. - Recharges target BlueLife based on {:?}% BluePower.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Recharge| - Item::RechargePlus | - Item::RechargePlusPlus => format!( - "Recharge RedLife and BlueLife based on {:?}% RedPower and BluePower.", - self.into_skill().unwrap().multiplier()), - - Item::Ruin| - Item::RuinPlus | - Item::RuinPlusPlus => format!( - "Team wide skill. Stun each construct for {:?}T. - Deal {:?}% BluePower as blue damage to each construct.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Link| - Item::LinkPlus | - Item::LinkPlusPlus => format!( - "Stun target for {:?}T. - Deal blue damage of {:?}% BluePower multiplied by number of effects on target.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Silence| - Item::SilencePlus | - Item::SilencePlusPlus => format!( - "Disable the target from using blue skills for {:?}T and deals {:?}% BluePower as blue damage. - Deals 45% more Damage per blue skill on target.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Slay| - Item::SlayPlus | - Item::SlayPlusPlus => format!( - "Deals {:?}% RedPower + {:?}% GreenPower as red damage. - Construct heals self for 50% of damage dealt to target construct GreenLife.", - self.into_skill().unwrap().multiplier(), - self.into_skill().unwrap().multiplier()), - - Item::Sleep| - Item::SleepPlus | - Item::SleepPlusPlus => format!( - "Stun for {:?}T and heal target for {:?}% GreenPower.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Restrict| - Item::RestrictPlus | - Item::RestrictPlusPlus => format!( - "Disable the target from using red skills for {:?}T and deals {:?}% RedPower as red damage. - Deals 35% more damage per red skill on target.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Bash| - Item::BashPlus | - Item::BashPlusPlus => format!( - "Bash the target increasing the cooldowns of target skills by 1T. Stuns target for {:?}T. - Deals {:?}% RedPower as red damage and 45% more damage per cooldown increased.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier()), - - Item::Strike| - Item::StrikePlus | - Item::StrikePlusPlus => format!( - "Strike the target with speed dealing {:?}% RedPower as red damage.", - self.into_skill().unwrap().multiplier()), - - Item::Siphon| - Item::SiphonPlus | - Item::SiphonPlusPlus => format!( - "Deals {:?}% BluePower + {:?}% GreenPower as blue damage each turn. - Construct heals self for 100% of damage dealt to target construct GreenLife. - Lasts {:?}T.", - self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), - self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), - self.into_skill().unwrap().effect()[0].get_duration()), - - Item::Intercept| - Item::InterceptPlus | - Item::InterceptPlusPlus => format!( - "Intercept redirects skills against the team to target, lasts {:?}T. - Recharges RedLife for {:?} RedPower.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().multiplier()), - - Item::Break| - Item::BreakPlus | - Item::BreakPlusPlus => format!( - "Stun the target for {:?}T and applies Vulnerable increasing red damage taken by {:?}% for {:?}T.", - self.into_skill().unwrap().effect()[0].get_duration(), - self.into_skill().unwrap().effect()[1].get_multiplier() - 100, - self.into_skill().unwrap().effect()[1].get_duration()), - - Item::Triage| - Item::TriagePlus | - Item::TriagePlusPlus => format!( - "Heals target for {:?}% GreenPower each turn. Lasts {:?}T.", - self.into_skill().unwrap().effect()[0].get_skill().unwrap().multiplier(), - self.into_skill().unwrap().effect()[0].get_duration()), - } - } - - // !!!!!! - // IF YOU CHANGE A COMBO - // BE SURE TO EDIT BUTTONS.JSX TOO - // !!!!!! - - fn combo(&self) -> Vec { - match self { - Item::Intercept => vec![Item::Buff, Item::Red, Item::Red], - Item::Triage => vec![Item::Buff, Item::Green, Item::Green], - Item::Absorb => vec![Item::Buff, Item::Blue, Item::Blue], - Item::Amplify => vec![Item::Buff, Item::Red, Item::Blue], - Item::Haste => vec![Item::Buff, Item::Red, Item::Green], - Item::Hybrid => vec![Item::Buff, Item::Green, Item::Blue], - - Item::InterceptPlus => vec![Item::Intercept, Item::Intercept], - Item::InterceptPlusPlus => vec![Item::InterceptPlus, Item::InterceptPlus], - - Item::TriagePlus => vec![Item::Triage, Item::Triage], - Item::TriagePlusPlus => vec![Item::TriagePlus, Item::TriagePlus], - - Item::AbsorbPlus => vec![Item::Absorb, Item::Absorb], - Item::AbsorbPlusPlus => vec![Item::AbsorbPlus, Item::AbsorbPlus], - - Item::AmplifyPlus => vec![Item::Amplify, Item::Amplify], - Item::AmplifyPlusPlus => vec![Item::AmplifyPlus, Item::AmplifyPlus], - - Item::HastePlus => vec![Item::Haste, Item::Haste], - Item::HastePlusPlus => vec![Item::HastePlus, Item::HastePlus], - - Item::HybridPlus => vec![Item::Hybrid, Item::Hybrid], - Item::HybridPlusPlus => vec![Item::HybridPlus, Item::HybridPlus], - - - Item::Purge => vec![Item::Debuff, Item::Green, Item::Green], - Item::Invert => vec![Item::Debuff, Item::Red, Item::Green], - Item::Restrict => vec![Item::Debuff, Item::Red, Item::Red], - Item::Silence => vec![Item::Debuff, Item::Blue, Item::Blue], - Item::Curse => vec![Item::Debuff, Item::Red, Item::Blue], - Item::Decay => vec![Item::Debuff, Item::Green, Item::Blue], - - Item::PurgePlus => vec![Item::Purge, Item::Purge], - Item::PurgePlusPlus => vec![Item::PurgePlus, Item::PurgePlus], - - Item::InvertPlus => vec![Item::Invert, Item::Invert], - Item::InvertPlusPlus => vec![Item::InvertPlus, Item::InvertPlus], - - Item::RestrictPlus => vec![Item::Restrict, Item::Restrict], - Item::RestrictPlusPlus => vec![Item::RestrictPlus, Item::RestrictPlus], - - Item::SilencePlus => vec![Item::Silence, Item::Silence], - Item::SilencePlusPlus => vec![Item::SilencePlus, Item::SilencePlus], - - Item::CursePlus => vec![Item::Curse, Item::Curse], - Item::CursePlusPlus => vec![Item::CursePlus, Item::CursePlus], - - Item::DecayPlus => vec![Item::Decay, Item::Decay], - Item::DecayPlusPlus => vec![Item::DecayPlus, Item::DecayPlus], - - - Item::Counter => vec![Item::Block, Item::Red, Item::Red], - Item::Reflect => vec![Item::Block, Item::Green, Item::Blue], - Item::Purify => vec![Item::Block, Item::Green, Item::Green], - Item::Sustain => vec![Item::Block, Item::Red, Item::Green], - Item::Electrify => vec![Item::Block, Item::Blue, Item::Blue], - Item::Recharge => vec![Item::Block, Item::Red, Item::Blue], - - Item::CounterPlus => vec![Item::Counter, Item::Counter], - Item::CounterPlusPlus => vec![Item::CounterPlus, Item::CounterPlus], - - Item::PurifyPlus => vec![Item::Purify, Item::Purify], - Item::PurifyPlusPlus => vec![Item::PurifyPlus, Item::PurifyPlus], - - Item::ElectrifyPlus => vec![Item::Electrify, Item::Electrify], - Item::ElectrifyPlusPlus => vec![Item::ElectrifyPlus, Item::ElectrifyPlus], - - Item::SustainPlus => vec![Item::Sustain, Item::Sustain], - Item::SustainPlusPlus => vec![Item::SustainPlus, Item::SustainPlus], - - Item::ReflectPlus => vec![Item::Reflect, Item::Reflect], - Item::ReflectPlusPlus => vec![Item::ReflectPlus, Item::ReflectPlus, ], - - Item::RechargePlus => vec![Item::Recharge, Item::Recharge], - Item::RechargePlusPlus => vec![Item::RechargePlus, Item::RechargePlus], - - Item::Bash => vec![Item::Stun, Item::Red, Item::Red], - Item::Sleep => vec![Item::Stun, Item::Green, Item::Green], - Item::Ruin => vec![Item::Stun, Item::Blue, Item::Blue], - Item::Link => vec![Item::Stun, Item::Blue, Item::Green], - Item::Banish => vec![Item::Stun, Item::Red, Item::Blue], - Item::Break => vec![Item::Stun, Item::Red, Item::Green], - - Item::BashPlus => vec![Item::Bash, Item::Bash], - Item::BashPlusPlus => vec![Item::BashPlus, Item::BashPlus], - - Item::SleepPlus => vec![Item::Sleep, Item::Sleep], - Item::SleepPlusPlus => vec![Item::SleepPlus, Item::SleepPlus,], - - Item::RuinPlus => vec![Item::Ruin, Item::Ruin], - Item::RuinPlusPlus => vec![Item::RuinPlus, Item::RuinPlus], - - Item::LinkPlus => vec![Item::Link, Item::Link], - Item::LinkPlusPlus => vec![Item::LinkPlus, Item::LinkPlus], - - Item::BanishPlus => vec![Item::Banish, Item::Banish], - Item::BanishPlusPlus => vec![Item::BanishPlus, Item::BanishPlus], - - Item::BreakPlus => vec![Item::Break, Item::Break], - Item::BreakPlusPlus => vec![Item::BreakPlus, Item::BreakPlus], - - - Item::Strike => vec![Item::Attack, Item::Red, Item::Red], - Item::Chaos => vec![Item::Attack, Item::Red, Item::Blue], - Item::Heal => vec![Item::Attack, Item::Green, Item::Green], - Item::Blast => vec![Item::Attack, Item::Blue, Item::Blue], - Item::Slay => vec![Item::Attack, Item::Red, Item::Green], - Item::Siphon => vec![Item::Attack, Item::Green, Item::Blue], - - Item::StrikePlus => vec![Item::Strike, Item::Strike], - Item::StrikePlusPlus => vec![Item::StrikePlus, Item::StrikePlus], - - Item::ChaosPlus => vec![Item::Chaos, Item::Chaos], - Item::ChaosPlusPlus => vec![Item::ChaosPlus, Item::ChaosPlus], - - Item::HealPlus => vec![Item::Heal, Item::Heal], - Item::HealPlusPlus => vec![Item::HealPlus, Item::HealPlus], - - Item::BlastPlus => vec![Item::Blast, Item::Blast], - Item::BlastPlusPlus => vec![Item::BlastPlus, Item::BlastPlus], - - Item::SlayPlus => vec![Item::Slay, Item::Slay], - Item::SlayPlusPlus => vec![Item::SlayPlus, Item::SlayPlus], - - Item::SiphonPlus => vec![Item::Siphon, Item::Siphon], - Item::SiphonPlusPlus => vec![Item::SiphonPlus, Item::SiphonPlus], - - Item::PowerRR => vec![Item::Power, Item::Red, Item::Red], - Item::PowerGG => vec![Item::Power, Item::Green, Item::Green], - Item::PowerBB => vec![Item::Power, Item::Blue, Item::Blue], - Item::PowerRG => vec![Item::Power, Item::Red, Item::Green], - Item::PowerGB => vec![Item::Power, Item::Green, Item::Blue], - Item::PowerRB => vec![Item::Power, Item::Red, Item::Blue], - Item::PowerRRPlus => vec![Item::PowerRR, Item::PowerRR], - Item::PowerGGPlus => vec![Item::PowerGG, Item::PowerGG], - Item::PowerBBPlus => vec![Item::PowerBB, Item::PowerBB], - Item::PowerRGPlus => vec![Item::PowerRG, Item::PowerRG], - Item::PowerGBPlus => vec![Item::PowerGB, Item::PowerGB], - Item::PowerRBPlus => vec![Item::PowerRB, Item::PowerRB], - Item::PowerRRPlusPlus => vec![Item::PowerRRPlus, Item::PowerRRPlus], - Item::PowerGGPlusPlus => vec![Item::PowerGGPlus, Item::PowerGGPlus], - Item::PowerBBPlusPlus => vec![Item::PowerBBPlus, Item::PowerBBPlus], - Item::PowerRGPlusPlus => vec![Item::PowerRGPlus, Item::PowerRGPlus], - Item::PowerGBPlusPlus => vec![Item::PowerGBPlus, Item::PowerGBPlus], - Item::PowerRBPlusPlus => vec![Item::PowerRBPlus, Item::PowerRBPlus], - - Item::LifeRR => vec![Item::Life, Item::Red, Item::Red], - Item::LifeGG => vec![Item::Life, Item::Green, Item::Green], - Item::LifeBB => vec![Item::Life, Item::Blue, Item::Blue], - Item::LifeRG => vec![Item::Life, Item::Red, Item::Green], - Item::LifeGB => vec![Item::Life, Item::Green, Item::Blue], - Item::LifeRB => vec![Item::Life, Item::Red, Item::Blue], - Item::LifeRRPlus => vec![Item::LifeRR, Item::LifeRR], - Item::LifeGGPlus => vec![Item::LifeGG, Item::LifeGG], - Item::LifeBBPlus => vec![Item::LifeBB, Item::LifeBB], - Item::LifeRGPlus => vec![Item::LifeRG, Item::LifeRG], - Item::LifeGBPlus => vec![Item::LifeGB, Item::LifeGB], - Item::LifeRBPlus => vec![Item::LifeRB, Item::LifeRB], - Item::LifeRRPlusPlus => vec![Item::LifeRRPlus, Item::LifeRRPlus], - Item::LifeGGPlusPlus => vec![Item::LifeGGPlus, Item::LifeGGPlus], - Item::LifeBBPlusPlus => vec![Item::LifeBBPlus, Item::LifeBBPlus], - Item::LifeRGPlusPlus => vec![Item::LifeRGPlus, Item::LifeRGPlus], - Item::LifeGBPlusPlus => vec![Item::LifeGBPlus, Item::LifeGBPlus], - Item::LifeRBPlusPlus => vec![Item::LifeRBPlus, Item::LifeRBPlus], - - Item::SpeedRR => vec![Item::Speed, Item::Red, Item::Red], - Item::SpeedGG => vec![Item::Speed, Item::Green, Item::Green], - Item::SpeedBB => vec![Item::Speed, Item::Blue, Item::Blue], - Item::SpeedRG => vec![Item::Speed, Item::Red, Item::Green], - Item::SpeedGB => vec![Item::Speed, Item::Green, Item::Blue], - Item::SpeedRB => vec![Item::Speed, Item::Red, Item::Blue], - Item::SpeedRRPlus => vec![Item::SpeedRR, Item::SpeedRR], - Item::SpeedGGPlus => vec![Item::SpeedGG, Item::SpeedGG], - Item::SpeedBBPlus => vec![Item::SpeedBB, Item::SpeedBB], - Item::SpeedRGPlus => vec![Item::SpeedRG, Item::SpeedRG], - Item::SpeedGBPlus => vec![Item::SpeedGB, Item::SpeedGB], - Item::SpeedRBPlus => vec![Item::SpeedRB, Item::SpeedRB], - Item::SpeedRRPlusPlus => vec![Item::SpeedRRPlus, Item::SpeedRRPlus], - Item::SpeedGGPlusPlus => vec![Item::SpeedGGPlus, Item::SpeedGGPlus], - Item::SpeedBBPlusPlus => vec![Item::SpeedBBPlus, Item::SpeedBBPlus], - Item::SpeedRGPlusPlus => vec![Item::SpeedRGPlus, Item::SpeedRGPlus], - Item::SpeedGBPlusPlus => vec![Item::SpeedGBPlus, Item::SpeedGBPlus], - Item::SpeedRBPlusPlus => vec![Item::SpeedRBPlus, Item::SpeedRBPlus], - - _ => vec![*self], - } - } -} - -impl From for Item { - fn from(skill: Skill) -> Item { - match skill { - Skill::Absorb => Item::Absorb, - Skill::AbsorbPlus => Item::AbsorbPlus, - Skill::AbsorbPlusPlus => Item::AbsorbPlusPlus, - Skill::Amplify => Item::Amplify, - Skill::AmplifyPlus => Item::AmplifyPlus, - Skill::AmplifyPlusPlus => Item::AmplifyPlusPlus, - Skill::Attack => Item::Attack, - Skill::Banish => Item::Banish, - Skill::BanishPlus => Item::BanishPlus, - Skill::BanishPlusPlus => Item::BanishPlusPlus, - Skill::Bash => Item::Bash, - Skill::BashPlus => Item::BashPlus, - Skill::BashPlusPlus => Item::BashPlusPlus, - Skill::Blast => Item::Blast, - Skill::BlastPlus => Item::BlastPlus, - Skill::BlastPlusPlus => Item::BlastPlusPlus, - Skill::Block => Item::Block, - Skill::Buff => Item::Buff, - Skill::Chaos => Item::Chaos, - Skill::ChaosPlus => Item::ChaosPlus, - Skill::ChaosPlusPlus => Item::ChaosPlusPlus, - Skill::Counter => Item::Counter, - Skill::CounterPlus => Item::CounterPlus, - Skill::CounterPlusPlus => Item::CounterPlusPlus, - Skill::Curse => Item::Curse, - Skill::CursePlus => Item::CursePlus, - Skill::CursePlusPlus => Item::CursePlusPlus, - Skill::Debuff => Item::Debuff, - Skill::Decay => Item::Decay, - Skill::DecayPlus => Item::DecayPlus, - Skill::DecayPlusPlus => Item::DecayPlusPlus, - Skill::Electrify => Item::Electrify, - Skill::ElectrifyPlus => Item::ElectrifyPlus, - Skill::ElectrifyPlusPlus=> Item::ElectrifyPlusPlus, - Skill::Haste => Item::Haste, - Skill::HastePlus => Item::HastePlus, - Skill::HastePlusPlus => Item::HastePlusPlus, - Skill::Heal => Item::Heal, - Skill::HealPlus => Item::HealPlus, - Skill::HealPlusPlus => Item::HealPlusPlus, - Skill::Hybrid => Item::Hybrid, - Skill::HybridPlus => Item::HybridPlus, - Skill::HybridPlusPlus => Item::HybridPlusPlus, - Skill::Intercept => Item::Intercept, - Skill::InterceptPlus => Item::InterceptPlus, - Skill::InterceptPlusPlus=> Item::InterceptPlusPlus, - Skill::Invert => Item::Invert, - Skill::InvertPlus => Item::InvertPlus, - Skill::InvertPlusPlus => Item::InvertPlusPlus, - Skill::Purge => Item::Purge, - Skill::PurgePlus => Item::PurgePlus, - Skill::PurgePlusPlus => Item::PurgePlusPlus, - Skill::Purify => Item::Purify, - Skill::PurifyPlus => Item::PurifyPlus, - Skill::PurifyPlusPlus => Item::PurifyPlusPlus, - Skill::Recharge => Item::Recharge, - Skill::RechargePlus => Item::RechargePlus, - Skill::RechargePlusPlus => Item::RechargePlusPlus, - Skill::Reflect => Item::Reflect, - Skill::ReflectPlus => Item::ReflectPlus, - Skill::ReflectPlusPlus => Item::ReflectPlusPlus, - Skill::Restrict => Item::Restrict, - Skill::RestrictPlus => Item::RestrictPlus, - Skill::RestrictPlusPlus => Item::RestrictPlusPlus, - Skill::Ruin => Item::Ruin, - Skill::RuinPlus => Item::RuinPlus, - Skill::RuinPlusPlus => Item::RuinPlusPlus, - Skill::Link => Item::Link, - Skill::LinkPlus => Item::LinkPlus, - Skill::LinkPlusPlus => Item::LinkPlusPlus, - Skill::Silence => Item::Silence, - Skill::SilencePlus => Item::SilencePlus, - Skill::SilencePlusPlus => Item::SilencePlusPlus, - Skill::Siphon => Item::Siphon, - Skill::SiphonPlus => Item::SiphonPlus, - Skill::SiphonPlusPlus => Item::SiphonPlusPlus, - Skill::Slay => Item::Slay, - Skill::SlayPlus => Item::SlayPlus, - Skill::SlayPlusPlus => Item::SlayPlusPlus, - Skill::Sleep => Item::Sleep, - Skill::SleepPlus => Item::SleepPlus, - Skill::SleepPlusPlus => Item::SleepPlusPlus, - Skill::Strike => Item::Strike, - Skill::StrikePlus => Item::StrikePlus, - Skill::StrikePlusPlus => Item::StrikePlusPlus, - Skill::Stun => Item::Stun, - Skill::Sustain => Item::Sustain, - Skill::SustainPlus => Item::SustainPlus, - Skill::SustainPlusPlus => Item::SustainPlusPlus, - Skill::Break => Item::Break, - Skill::BreakPlus => Item::BreakPlus, - Skill::BreakPlusPlus => Item::BreakPlusPlus, - Skill::Triage => Item::Triage, - Skill::TriagePlus => Item::TriagePlus, - Skill::TriagePlusPlus => Item::TriagePlusPlus, - - // Convert subskills into parent skills - Skill::Electrocute => Item::Electrify, - Skill::ElectrocutePlus => Item::ElectrifyPlus, - Skill::ElectrocutePlusPlus => Item::ElectrifyPlusPlus, - Skill::ElectrocuteTick => Item::Electrify, - Skill::ElectrocuteTickPlus => Item::ElectrifyPlus, - Skill::ElectrocuteTickPlusPlus => Item::ElectrifyPlus, - Skill::DecayTick => Item::Decay, - Skill::DecayTickPlus => Item::DecayPlus, - Skill::DecayTickPlusPlus => Item::DecayPlusPlus, - Skill::Absorption => Item::Absorb, - Skill::AbsorptionPlus => Item::AbsorbPlus, - Skill::AbsorptionPlusPlus => Item::AbsorbPlusPlus, - Skill::HasteStrike => Item::Haste, - Skill::HybridBlast => Item::Hybrid, - Skill::CounterAttack => Item::Counter, - Skill::CounterAttackPlus => Item::CounterPlus, - Skill::CounterAttackPlusPlus => Item::CounterPlusPlus, - Skill::SiphonTick => Item::Siphon, - Skill::SiphonTickPlus => Item::SiphonPlus, - Skill::SiphonTickPlusPlus => Item::SiphonPlusPlus, - Skill::TriageTick => Item::Triage, - Skill::TriageTickPlus => Item::TriagePlus, - Skill::TriageTickPlusPlus => Item::TriagePlusPlus, - } - } -} - -impl From for Item { - fn from(spec: Spec) -> Item { - match spec { - Spec::Speed => Item::Speed, - Spec::SpeedRR => Item::SpeedRR, - Spec::SpeedBB => Item::SpeedBB, - Spec::SpeedGG => Item::SpeedGG, - Spec::SpeedRG => Item::SpeedRG, - Spec::SpeedGB => Item::SpeedGB, - Spec::SpeedRB => Item::SpeedRB, - - Spec::SpeedRRPlus => Item::SpeedRRPlus, - Spec::SpeedBBPlus => Item::SpeedBBPlus, - Spec::SpeedGGPlus => Item::SpeedGGPlus, - Spec::SpeedRGPlus => Item::SpeedRGPlus, - Spec::SpeedGBPlus => Item::SpeedGBPlus, - Spec::SpeedRBPlus => Item::SpeedRBPlus, - - Spec::SpeedRRPlusPlus => Item::SpeedRRPlusPlus, - Spec::SpeedBBPlusPlus => Item::SpeedBBPlusPlus, - Spec::SpeedGGPlusPlus => Item::SpeedGGPlusPlus, - Spec::SpeedRGPlusPlus => Item::SpeedRGPlusPlus, - Spec::SpeedGBPlusPlus => Item::SpeedGBPlusPlus, - Spec::SpeedRBPlusPlus => Item::SpeedRBPlusPlus, - - Spec::Power => Item::Power, - Spec::PowerRR => Item::PowerRR, - Spec::PowerBB => Item::PowerBB, - Spec::PowerGG => Item::PowerGG, - Spec::PowerRG => Item::PowerRG, - Spec::PowerGB => Item::PowerGB, - Spec::PowerRB => Item::PowerRB, - Spec::PowerRRPlus => Item::PowerRRPlus, - Spec::PowerBBPlus => Item::PowerBBPlus, - Spec::PowerGGPlus => Item::PowerGGPlus, - Spec::PowerRGPlus => Item::PowerRGPlus, - Spec::PowerGBPlus => Item::PowerGBPlus, - Spec::PowerRBPlus => Item::PowerRBPlus, - Spec::PowerRRPlusPlus => Item::PowerRRPlusPlus, - Spec::PowerBBPlusPlus => Item::PowerBBPlusPlus, - Spec::PowerGGPlusPlus => Item::PowerGGPlusPlus, - Spec::PowerRGPlusPlus => Item::PowerRGPlusPlus, - Spec::PowerGBPlusPlus => Item::PowerGBPlusPlus, - Spec::PowerRBPlusPlus => Item::PowerRBPlusPlus, - - Spec::Life => Item::Life, - Spec::LifeRG => Item::LifeRG, - Spec::LifeGB => Item::LifeGB, - Spec::LifeRB => Item::LifeRB, - Spec::LifeGG => Item::LifeGG, - Spec::LifeRR => Item::LifeRR, - Spec::LifeBB => Item::LifeBB, - Spec::LifeRGPlus => Item::LifeRGPlus, - Spec::LifeGBPlus => Item::LifeGBPlus, - Spec::LifeRBPlus => Item::LifeRBPlus, - Spec::LifeGGPlus => Item::LifeGGPlus, - Spec::LifeRRPlus => Item::LifeRRPlus, - Spec::LifeBBPlus => Item::LifeBBPlus, - Spec::LifeRGPlusPlus => Item::LifeRGPlusPlus, - Spec::LifeGBPlusPlus => Item::LifeGBPlusPlus, - Spec::LifeRBPlusPlus => Item::LifeRBPlusPlus, - Spec::LifeGGPlusPlus => Item::LifeGGPlusPlus, - Spec::LifeRRPlusPlus => Item::LifeRRPlusPlus, - Spec::LifeBBPlusPlus => Item::LifeBBPlusPlus, - - // _ => panic!("{:?} not implemented as a item", spec), - } - } -} - - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct Combo { - pub item: Item, - pub components: Vec, -} - -pub fn get_combos() -> Vec { - let mut combinations = vec![ - Combo { components: Item::Intercept.combo(), item: Item::Intercept }, - Combo { components: Item::InterceptPlus.combo(), item: Item::InterceptPlus }, - Combo { components: Item::InterceptPlusPlus.combo(), item: Item::InterceptPlusPlus }, - Combo { components: Item::Triage.combo(), item: Item::Triage }, - Combo { components: Item::TriagePlus.combo(), item: Item::TriagePlus }, - Combo { components: Item::TriagePlusPlus.combo(), item: Item::TriagePlusPlus }, - Combo { components: Item::Absorb.combo(), item: Item::Absorb }, - Combo { components: Item::AbsorbPlus.combo(), item: Item::AbsorbPlus }, - Combo { components: Item::AbsorbPlusPlus.combo(), item: Item::AbsorbPlusPlus }, - Combo { components: Item::Haste.combo(), item: Item::Haste }, - Combo { components: Item::HastePlus.combo(), item: Item::HastePlus }, - Combo { components: Item::HastePlusPlus.combo(), item: Item::HastePlusPlus }, - Combo { components: Item::Hybrid.combo(), item: Item::Hybrid }, - Combo { components: Item::HybridPlus.combo(), item: Item::HybridPlus }, - Combo { components: Item::HybridPlusPlus.combo(), item: Item::HybridPlusPlus }, - Combo { components: Item::Amplify.combo(), item: Item::Amplify }, - Combo { components: Item::AmplifyPlus.combo(), item: Item::AmplifyPlus }, - Combo { components: Item::AmplifyPlusPlus.combo(), item: Item::AmplifyPlusPlus }, - - Combo { components: Item::Restrict.combo(), item: Item::Restrict }, - Combo { components: Item::RestrictPlus.combo(), item: Item::RestrictPlus }, - Combo { components: Item::RestrictPlusPlus.combo(), item: Item::RestrictPlusPlus }, - Combo { components: Item::Purge.combo(), item: Item::Purge }, - Combo { components: Item::PurgePlus.combo(), item: Item::PurgePlus }, - Combo { components: Item::PurgePlusPlus.combo(), item: Item::PurgePlusPlus }, - - Combo { components: Item::Silence.combo(), item: Item::Silence }, - Combo { components: Item::SilencePlus.combo(), item: Item::SilencePlus }, - Combo { components: Item::SilencePlusPlus.combo(), item: Item::SilencePlusPlus }, - - - Combo { components: Item::Invert.combo(), item: Item::Invert }, - Combo { components: Item::InvertPlus.combo(), item: Item::InvertPlus }, - Combo { components: Item::InvertPlusPlus.combo(), item: Item::InvertPlusPlus }, - Combo { components: Item::Curse.combo(), item: Item::Curse }, - Combo { components: Item::CursePlus.combo(), item: Item::CursePlus }, - Combo { components: Item::CursePlusPlus.combo(), item: Item::CursePlusPlus }, - Combo { components: Item::Decay.combo(), item: Item::Decay }, - Combo { components: Item::DecayPlus.combo(), item: Item::DecayPlus }, - Combo { components: Item::DecayPlusPlus.combo(), item: Item::DecayPlusPlus }, - - Combo { components: Item::Counter.combo(), item: Item::Counter }, - Combo { components: Item::CounterPlus.combo(), item: Item::CounterPlus }, - Combo { components: Item::CounterPlusPlus.combo(), item: Item::CounterPlusPlus }, - Combo { components: Item::Purify.combo(), item: Item::Purify }, - Combo { components: Item::PurifyPlus.combo(), item: Item::PurifyPlus }, - Combo { components: Item::PurifyPlusPlus.combo(), item: Item::PurifyPlusPlus }, - - Combo { components: Item::Electrify.combo(), item: Item::Electrify }, - Combo { components: Item::ElectrifyPlus.combo(), item: Item::ElectrifyPlus }, - Combo { components: Item::ElectrifyPlusPlus.combo(), item: Item::ElectrifyPlusPlus }, - - Combo { components: Item::Sustain.combo(), item: Item::Sustain }, - Combo { components: Item::SustainPlus.combo(), item: Item::SustainPlus }, - Combo { components: Item::SustainPlusPlus.combo(), item: Item::SustainPlusPlus }, - Combo { components: Item::Reflect.combo(), item: Item::Reflect }, - Combo { components: Item::ReflectPlus.combo(), item: Item::ReflectPlus }, - Combo { components: Item::ReflectPlusPlus.combo(), item: Item::ReflectPlusPlus }, - - - Combo { components: Item::Recharge.combo(), item: Item::Recharge }, - Combo { components: Item::RechargePlus.combo(), item: Item::RechargePlus }, - Combo { components: Item::RechargePlusPlus.combo(), item: Item::RechargePlusPlus }, - - Combo { components: Item::Bash.combo(), item: Item::Bash }, - Combo { components: Item::BashPlus.combo(), item: Item::BashPlus }, - Combo { components: Item::BashPlusPlus.combo(), item: Item::BashPlusPlus }, - Combo { components: Item::Sleep.combo(), item: Item::Sleep }, - Combo { components: Item::SleepPlus.combo(), item: Item::SleepPlus }, - Combo { components: Item::SleepPlusPlus.combo(), item: Item::SleepPlusPlus }, - Combo { components: Item::Ruin.combo(), item: Item::Ruin }, - Combo { components: Item::RuinPlus.combo(), item: Item::RuinPlus }, - Combo { components: Item::RuinPlusPlus.combo(), item: Item::RuinPlusPlus }, - - Combo { components: Item::Break.combo(), item: Item::Break }, - Combo { components: Item::BreakPlus.combo(), item: Item::BreakPlus }, - Combo { components: Item::BreakPlusPlus.combo(), item: Item::BreakPlusPlus }, - Combo { components: Item::Link.combo(), item: Item::Link }, - Combo { components: Item::LinkPlus.combo(), item: Item::LinkPlus }, - Combo { components: Item::LinkPlusPlus.combo(), item: Item::LinkPlusPlus }, - Combo { components: Item::Banish.combo(), item: Item::Banish }, - Combo { components: Item::BanishPlus.combo(), item: Item::BanishPlus }, - Combo { components: Item::BanishPlusPlus.combo(), item: Item::BanishPlusPlus }, - - Combo { components: Item::Strike.combo(), item: Item::Strike }, - Combo { components: Item::StrikePlus.combo(), item: Item::StrikePlus }, - Combo { components: Item::StrikePlusPlus.combo(), item: Item::StrikePlusPlus }, - - Combo { components: Item::Heal.combo(), item: Item::Heal }, - Combo { components: Item::HealPlus.combo(), item: Item::HealPlus }, - Combo { components: Item::HealPlusPlus.combo(), item: Item::HealPlusPlus }, - Combo { components: Item::Blast.combo(), item: Item::Blast }, - Combo { components: Item::BlastPlus.combo(), item: Item::BlastPlus }, - Combo { components: Item::BlastPlusPlus.combo(), item: Item::BlastPlusPlus }, - Combo { components: Item::Slay.combo(), item: Item::Slay }, - Combo { components: Item::SlayPlus.combo(), item: Item::SlayPlus }, - Combo { components: Item::SlayPlusPlus.combo(), item: Item::SlayPlusPlus }, - Combo { components: Item::Siphon.combo(), item: Item::Siphon }, - Combo { components: Item::SiphonPlus.combo(), item: Item::SiphonPlus }, - Combo { components: Item::SiphonPlusPlus.combo(), item: Item::SiphonPlusPlus }, - Combo { components: Item::Chaos.combo(), item: Item::Chaos }, - Combo { components: Item::ChaosPlus.combo(), item: Item::ChaosPlus }, - Combo { components: Item::ChaosPlusPlus.combo(), item: Item::ChaosPlusPlus }, - - Combo { components: Item::PowerRR.combo(), item: Item::PowerRR }, - Combo { components: Item::PowerGG.combo(), item: Item::PowerGG }, - Combo { components: Item::PowerBB.combo(), item: Item::PowerBB }, - Combo { components: Item::PowerRG.combo(), item: Item::PowerRG }, - Combo { components: Item::PowerGB.combo(), item: Item::PowerGB }, - Combo { components: Item::PowerRB.combo(), item: Item::PowerRB }, - Combo { components: Item::PowerRRPlus.combo(), item: Item::PowerRRPlus }, - Combo { components: Item::PowerGGPlus.combo(), item: Item::PowerGGPlus }, - Combo { components: Item::PowerBBPlus.combo(), item: Item::PowerBBPlus }, - Combo { components: Item::PowerRGPlus.combo(), item: Item::PowerRGPlus }, - Combo { components: Item::PowerGBPlus.combo(), item: Item::PowerGBPlus }, - Combo { components: Item::PowerRBPlus.combo(), item: Item::PowerRBPlus }, - Combo { components: Item::PowerRRPlusPlus.combo(), item: Item::PowerRRPlusPlus }, - Combo { components: Item::PowerGGPlusPlus.combo(), item: Item::PowerGGPlusPlus }, - Combo { components: Item::PowerBBPlusPlus.combo(), item: Item::PowerBBPlusPlus }, - Combo { components: Item::PowerRGPlusPlus.combo(), item: Item::PowerRGPlusPlus }, - Combo { components: Item::PowerGBPlusPlus.combo(), item: Item::PowerGBPlusPlus }, - Combo { components: Item::PowerRBPlusPlus.combo(), item: Item::PowerRBPlusPlus }, - - Combo { components: Item::LifeRR.combo(), item: Item::LifeRR}, - Combo { components: Item::LifeGG.combo(), item: Item::LifeGG}, - Combo { components: Item::LifeBB.combo(), item: Item::LifeBB}, - Combo { components: Item::LifeRG.combo(), item: Item::LifeRG}, - Combo { components: Item::LifeGB.combo(), item: Item::LifeGB}, - Combo { components: Item::LifeRB.combo(), item: Item::LifeRB}, - Combo { components: Item::LifeRRPlus.combo(), item: Item::LifeRRPlus }, - Combo { components: Item::LifeGGPlus.combo(), item: Item::LifeGGPlus }, - Combo { components: Item::LifeBBPlus.combo(), item: Item::LifeBBPlus }, - Combo { components: Item::LifeRGPlus.combo(), item: Item::LifeRGPlus }, - Combo { components: Item::LifeGBPlus.combo(), item: Item::LifeGBPlus }, - Combo { components: Item::LifeRBPlus.combo(), item: Item::LifeRBPlus }, - Combo { components: Item::LifeRRPlusPlus.combo(), item: Item::LifeRRPlusPlus }, - Combo { components: Item::LifeGGPlusPlus.combo(), item: Item::LifeGGPlusPlus }, - Combo { components: Item::LifeBBPlusPlus.combo(), item: Item::LifeBBPlusPlus }, - Combo { components: Item::LifeRGPlusPlus.combo(), item: Item::LifeRGPlusPlus }, - Combo { components: Item::LifeGBPlusPlus.combo(), item: Item::LifeGBPlusPlus }, - Combo { components: Item::LifeRBPlusPlus.combo(), item: Item::LifeRBPlusPlus }, - - Combo { components: Item::SpeedRR.combo(), item: Item::SpeedRR}, - Combo { components: Item::SpeedGG.combo(), item: Item::SpeedGG}, - Combo { components: Item::SpeedBB.combo(), item: Item::SpeedBB}, - Combo { components: Item::SpeedRG.combo(), item: Item::SpeedRG}, - Combo { components: Item::SpeedGB.combo(), item: Item::SpeedGB}, - Combo { components: Item::SpeedRB.combo(), item: Item::SpeedRB}, - Combo { components: Item::SpeedRRPlus.combo(), item: Item::SpeedRRPlus }, - Combo { components: Item::SpeedGGPlus.combo(), item: Item::SpeedGGPlus }, - Combo { components: Item::SpeedBBPlus.combo(), item: Item::SpeedBBPlus }, - Combo { components: Item::SpeedRGPlus.combo(), item: Item::SpeedRGPlus }, - Combo { components: Item::SpeedGBPlus.combo(), item: Item::SpeedGBPlus }, - Combo { components: Item::SpeedRBPlus.combo(), item: Item::SpeedRBPlus }, - Combo { components: Item::SpeedRRPlusPlus.combo(), item: Item::SpeedRRPlusPlus }, - Combo { components: Item::SpeedGGPlusPlus.combo(), item: Item::SpeedGGPlusPlus }, - Combo { components: Item::SpeedBBPlusPlus.combo(), item: Item::SpeedBBPlusPlus }, - Combo { components: Item::SpeedRGPlusPlus.combo(), item: Item::SpeedRGPlusPlus }, - Combo { components: Item::SpeedGBPlusPlus.combo(), item: Item::SpeedGBPlusPlus }, - Combo { components: Item::SpeedRBPlusPlus.combo(), item: Item::SpeedRBPlusPlus }, - ]; - - combinations.iter_mut().for_each(|set| set.components.sort_unstable()); - - return combinations; -} - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct ItemInfo { - pub item: Item, - pub cost: usize, - pub spec: bool, - pub values: Option, - pub skill: bool, - pub speed: Option, - pub cooldown: Cooldown, - pub description: String, -} - - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct ItemInfoCtr { - pub combos: Vec, - pub items: Vec, -} - -pub fn item_info() -> ItemInfoCtr { - let combos = get_combos(); - let mut items = combos - .into_iter() - .flat_map(|mut c| { - c.components.push(c.item); - c.components - }) - .collect::>(); - - items.sort_unstable(); - items.dedup(); - - let items = items - .into_iter() - .map(|v| ItemInfo { - item: v, - cost: v.cost(), - spec: v.into_spec().is_some(), - values: match v.into_spec() { - Some(s) => Some(s.values()), - None => None - }, - skill: v.into_skill().is_some(), - description: v.into_description(), - speed: match v.into_skill() { - Some(s) => Some(s.speed()), - None => None - }, - cooldown: match v.into_skill() { - Some(s) => s.base_cd(), - None => None - }, - }) - .collect::>(); - - let combos = get_combos(); - - return ItemInfoCtr { - combos, - items, - }; -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn item_components_test() { - assert_eq!(Item::Strike.components(), vec![Item::Red, Item::Red, Item::Attack]); - assert_eq!(Item::StrikePlus.components(), vec![ - Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, - ]); - assert_eq!(Item::StrikePlusPlus.components(), vec![ - Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, - - Item::Red, Item::Red, Item::Attack, - Item::Red, Item::Red, Item::Attack, - ]); - - } - - #[test] - fn item_info_test() { - item_info(); - } -} \ No newline at end of file diff --git a/server/src/lib.rs b/server/src/lib.rs index 776c653a..954197e4 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -33,28 +33,19 @@ extern crate lettre_email; extern crate ws; extern crate crossbeam_channel; +extern crate mnml_core; + mod account; -mod acp; -mod construct; -mod effect; -mod game; -mod instance; -mod item; +// mod acp; mod img; mod mail; -mod mob; mod mtx; mod names; mod http; mod payments; mod pg; -mod player; mod events; pub mod rpc; -mod skill; -mod spec; -mod util; -mod vbox; mod warden; use std::thread::{spawn}; diff --git a/server/src/mob.rs b/server/src/mob.rs deleted file mode 100644 index 2cab954b..00000000 --- a/server/src/mob.rs +++ /dev/null @@ -1,30 +0,0 @@ -use uuid::Uuid; - -use std::iter; - -use construct::{Construct}; -use names::{name}; -use player::{Player}; - -pub fn generate_mob() -> Construct { - let mob = Construct::new() - .named(&name()); - - return mob; -} - -pub fn instance_mobs(player_id: Uuid) -> Vec { - iter::repeat_with(|| - generate_mob() - .set_account(player_id)) - // .learn(Skill::Attack)) - .take(3) - .collect::>() -} - -pub fn bot_player() -> Player { - let bot_id = Uuid::new_v4(); - let constructs = instance_mobs(bot_id); - Player::new(bot_id, &name(), constructs).set_bot(true) -} - diff --git a/server/src/mtx.rs b/server/src/mtx.rs index b47daa5f..5fc66763 100644 --- a/server/src/mtx.rs +++ b/server/src/mtx.rs @@ -10,7 +10,9 @@ use failure::err_msg; use account; use account::Account; -use construct::{Construct, construct_select, construct_write, construct_spawn}; +use mnml_core::construct::{Construct}; + +use pg::{construct_select, construct_write, construct_spawn}; use names::{name as generate_name}; use img; diff --git a/server/src/passives.rs b/server/src/passives.rs deleted file mode 100644 index 7f19f0f0..00000000 --- a/server/src/passives.rs +++ /dev/null @@ -1,70 +0,0 @@ -use petgraph::graph::{Graph, UnGraph, NodeIndex}; -use petgraph::dot::{Dot, Config}; - -#[derive(Debug,Clone,Copy,PartialEq,Eq,Hash,PartialOrd,Ord,Serialize,Deserialize)] -pub struct Passive { - id: &'static str, - allocated: bool, - effect: usize, -} - -impl Passive { - fn new(id: &'static str) -> Passive { - return Passive { - id, - allocated: false, - effect: 0, - }; - } -} - -pub fn create_passive_graph() -> UnGraph { - let mut gr = Graph::new_undirected(); - - let start = gr.add_node(Passive::new("START")); - let mut last; - let mut next; - - // Natural Selection nodes - next = gr.add_node(Passive::new("NS")); - gr.add_edge(start, next, ()); - last = next; - - next = gr.add_node(Passive::new("NSPD0000")); - gr.add_edge(last, next, ()); - last = next; - - next = gr.add_node(Passive::new("NSPD0001")); - gr.add_edge(last, next, ()); - last = next; - - next = gr.add_node(Passive::new("NSPD0002")); - gr.add_edge(last, next, ()); - last = next; - - next = gr.add_node(Passive::new("NSPD0003")); - gr.add_edge(last, next, ()); - last = next; - - next = gr.add_node(Passive::new("NSBLOCK")); - gr.add_edge(last, next, ()); - last = next; - - return gr; -} - -#[cfg(test)] -mod tests { - use passives::*; - - #[test] - fn create_graph() { - let _graph = create_passive_graph(); - // good shit; - // let nodes = graph.node_indices().collect::>(); - // info!("{:?}", nodes[0]); - // info!("{:?}", graph.node_weight(nodes[0])); - - // info!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); - } -} diff --git a/server/src/pg.rs b/server/src/pg.rs index 2acd31f3..7a9f9495 100644 --- a/server/src/pg.rs +++ b/server/src/pg.rs @@ -1,22 +1,35 @@ +use std::fs::File; use std::env; use std::thread::spawn; use uuid::Uuid; +use failure::err_msg; use failure::Error; -use r2d2::{Pool}; -use r2d2::{PooledConnection}; +use r2d2::{Pool, PooledConnection}; use r2d2_postgres::{TlsMode, PostgresConnectionManager}; use fallible_iterator::{FallibleIterator}; +use postgres::transaction::Transaction; use crossbeam_channel::{Sender}; +use serde_cbor::{from_slice, to_vec}; + +use mnml_core::construct::{Construct, ConstructSkeleton}; +use mnml_core::game::{Game, Phase}; +use mnml_core::player::Player; +use mnml_core::mob::instance_mobs; +use mnml_core::vbox::{ItemType, VboxIndices}; +use mnml_core::item::Item; +use mnml_core::skill::Skill; +use mnml_core::mob::bot_player; +use mnml_core::instance::{Instance, TimeControl}; use events::{Event}; -use account; -use game; -use instance; use rpc::RpcMessage; +use account; +use account::{Account}; +use img; pub type Db = PooledConnection; pub type PgPool = Pool; @@ -88,9 +101,9 @@ fn handle_notification(n: Notification, pool: &PgPool, events: &Sender) { Table::Accounts => Some(Event::Push(n.id, RpcMessage::AccountState(account::select(&db, n.id).unwrap()))), Table::Instances => - Some(Event::Push(n.id, instance::instance_state(&mut tx, n.id).unwrap())), + Some(Event::Push(n.id, instance_state(&mut tx, n.id).unwrap())), Table::Games => - Some(Event::Push(n.id, RpcMessage::GameState(game::game_get(&mut tx, n.id).unwrap()))), + Some(Event::Push(n.id, RpcMessage::GameState(game_get(&mut tx, n.id).unwrap()))), _ => { // warn!("unimplemented update notification {:?}", n); None @@ -128,3 +141,721 @@ pub fn listen(pool: PgPool, events: Sender) -> Result<(), Error> { } } } + + +pub fn construct_delete(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result<(), Error> { + let query = " + DELETE + FROM constructs + WHERE id = $1 + and account = $2; + "; + + let result = tx + .execute(query, &[&id, &account_id])?; + + if result != 1 { + return Err(format_err!("unable to delete construct {:?}", id)); + } + + info!("construct deleted {:?}", id); + + return Ok(()); +} + +pub fn construct_get(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result { + let query = " + SELECT data + FROM constructs + WHERE id = $1 + AND account = $2; + "; + + let result = tx + .query(query, &[&id, &account_id])?; + + let result = result.iter().next().ok_or(format_err!("construct {:} not found", id))?; + let construct_bytes: Vec = result.get(0); + let skeleton = from_slice::(&construct_bytes)?; + + return Ok(Construct::from_skeleton(&skeleton)); +} + +pub fn construct_select(tx: &mut Transaction, id: Uuid, account_id: Uuid) -> Result { + let query = " + SELECT data + FROM constructs + WHERE id = $1 + AND account = $2 + FOR UPDATE; + "; + + let result = tx + .query(query, &[&id, &account_id])?; + + let result = result.iter().next().ok_or(format_err!("construct {:} not found", id))?; + let construct_bytes: Vec = result.get(0); + let skeleton = from_slice::(&construct_bytes)?; + + return Ok(Construct::from_skeleton(&skeleton)); +} + +pub fn construct_spawn(tx: &mut Transaction, account: Uuid, name: String, team: bool) -> Result { + let construct = Construct::new() + .named(&name) + .set_account(account); + + let construct_bytes = to_vec(&construct)?; + + let query = " + INSERT INTO constructs (id, account, data, team) + VALUES ($1, $2, $3, $4) + RETURNING id, account; + "; + + let result = tx + .query(query, &[&construct.id, &account, &construct_bytes, &team])?; + + let _returned = result.iter().next().ok_or(err_msg("no row returned"))?; + + img::shapes_write(construct.img)?; + + info!("spawned construct account={:} name={:?}", account, construct.name); + return Ok(construct); +} + +pub fn construct_write(tx: &mut Transaction, construct: Construct) -> Result { + let construct_bytes = to_vec(&construct.to_skeleton())?; + + let query = " + UPDATE constructs + SET data = $1, updated_at = now() + WHERE id = $2 + RETURNING id, account, data; + "; + + let result = tx + .query(query, &[&construct_bytes, &construct.id])?; + + let _returned = result.iter().next().expect("no row returned"); + + // info!("{:?} wrote construct", construct.id); + + return Ok(construct); +} +pub fn game_write(tx: &mut Transaction, game: &Game) -> Result<(), Error> { + let game_bytes = to_vec(&game)?; + + let query = " + INSERT INTO games (id, data, upkeep) + VALUES ($1, $2, $3) + RETURNING id; + "; + + // no games should be sent to db that are not in progress + let result = tx + .query(query, &[&game.id, &game_bytes, &game.phase_end])?; + + result.iter().next().ok_or(format_err!("no game written"))?; + + // info!("{:} wrote game", game.id); + + return Ok(()); +} + +pub fn game_state(tx: &mut Transaction, account: &Account, id: Uuid) -> Result { + Ok(game_get(tx, id)?.redact(account.id)) +} + +pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result { + let query = " + SELECT * + FROM games + WHERE id = $1 + FOR UPDATE; + "; + + let result = tx + .query(query, &[&id])?; + + let returned = match result.iter().next() { + Some(row) => row, + None => return Err(err_msg("game not found")), + }; + + // tells from_slice to cast into a construct + let game_bytes: Vec = returned.get("data"); + let game = from_slice::(&game_bytes)?; + + return Ok(game); +} + +pub fn select(db: &Db, id: Uuid) -> Result { + let query = " + SELECT * + FROM games + WHERE id = $1; + "; + + let result = db + .query(query, &[&id])?; + + let returned = match result.iter().next() { + Some(row) => row, + None => return Err(err_msg("game not found")), + }; + + // tells from_slice to cast into a construct + let game_bytes: Vec = returned.get("data"); + let game = from_slice::(&game_bytes)?; + + return Ok(game); +} + +pub fn list(db: &Db, number: u32) -> Result, Error> { + let query = " + SELECT data + FROM games + ORDER BY created_at + LIMIT $1; + "; + + let result = db + .query(query, &[&number])?; + + let mut list = vec![]; + + for row in result.into_iter() { + let bytes: Vec = row.get(0); + + match from_slice::(&bytes) { + Ok(i) => list.push(i), + Err(e) => { + warn!("{:?}", e); + } + }; + } + + return Ok(list); +} + +pub fn games_need_upkeep(tx: &mut Transaction) -> Result, Error> { + let query = " + SELECT data, id + FROM games + WHERE finished = false + AND upkeep < now() + FOR UPDATE; + "; + let result = tx + .query(query, &[])?; + + let mut list = vec![]; + + for row in result.into_iter() { + let bytes: Vec = row.get(0); + let id = row.get(1); + + match from_slice::(&bytes) { + Ok(i) => list.push(i), + Err(_e) => { + game_delete(tx, id)?; + } + }; + } + + return Ok(list); +} + +pub fn game_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { + let query = " + DELETE + FROM games + WHERE id = $1; + "; + + let result = tx + .execute(query, &[&id])?; + + if result != 1 { + return Err(format_err!("unable to delete player {:?}", id)); + } + + info!("game deleted {:?}", id); + + return Ok(()); +} + +pub fn game_update(tx: &mut Transaction, game: &Game) -> Result<(), Error> { + let game_bytes = to_vec(&game)?; + + let query = " + UPDATE games + SET data = $1, finished = $2, upkeep = $3, updated_at = now() + WHERE id = $4 + RETURNING id, data; + "; + + let result = tx + .query(query, &[&game_bytes, &game.finished(), &game.phase_end, &game.id])?; + + result.iter().next().ok_or(format_err!("game {:?} could not be written", game))?; + + if game.finished() { + info!("finished id={:?}", game.id); + + match game_json_file_write(&game) { + Ok(dest) => info!("wrote dest={:?}", dest), + Err(e) => error!("json write error={:?}", e), + }; + + if let Some(i) = game.instance { + instance_game_finished(tx, &game, i)?; + } + } + + return Ok(()); +} + +fn game_json_file_write(g: &Game) -> Result { + let dest = format!("/var/lib/mnml/data/games/{}.mnml.game.json", g.id); + serde_json::to_writer(File::create(&dest)?, g)?; + Ok(dest) +} + +pub fn game_skill(tx: &mut Transaction, account: &Account, game_id: Uuid, construct_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Result { + let mut game = game_get(tx, game_id)?; + + game.add_skill(account.id, construct_id, target_construct_id, skill)?; + + if game.skill_phase_finished() { + game = game.resolve_phase_start(); + } + + game_update(tx, &game)?; + + Ok(game) +} + +pub fn game_offer_draw(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { + let game = game_get(tx, game_id)? + .offer_draw(account.id)?; + + game_update(tx, &game)?; + + Ok(game) +} + +pub fn game_concede(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { + let game = game_get(tx, game_id)? + .concede(account.id)?; + + game_update(tx, &game)?; + + Ok(game) +} + + +pub fn game_skill_clear(tx: &mut Transaction, account: &Account, game_id: Uuid) -> Result { + let mut game = game_get(tx, game_id)?; + game.clear_skill(account.id)?; + game_update(tx, &game)?; + + Ok(game) +} + +pub fn game_ready(tx: &mut Transaction, account: &Account, id: Uuid) -> Result { + let mut game = game_get(tx, id)?; + + game.player_ready(account.id)?; + + if game.skill_phase_finished() { + game = game.resolve_phase_start(); + } + + game_update(tx, &game)?; + + Ok(game) +} + + + + + + + + + +pub fn instance_create(tx: &mut Transaction, instance: Instance) -> Result { + let instance_bytes = to_vec(&instance)?; + + let query = " + INSERT INTO instances (id, data, upkeep) + VALUES ($1, $2, $3) + RETURNING id; + "; + + let result = tx + .query(query, &[&instance.id, &instance_bytes, &instance.phase_end])?; + + result.iter().next().ok_or(format_err!("no instances written"))?; + + return Ok(instance); +} + +pub fn instance_update(tx: &mut Transaction, instance: Instance) -> Result { + let instance_bytes = to_vec(&instance)?; + + let query = " + UPDATE instances + SET data = $1, finished = $2, upkeep = $3, updated_at = now() + WHERE id = $4 + RETURNING id, data; + "; + + let result = tx + .query(query, &[&instance_bytes, &instance.finished(), &instance.phase_end, &instance.id])?; + + result.iter().next().ok_or(err_msg("no instance row returned"))?; + + trace!("{:?} wrote instance", instance.id); + + if instance.finished() { + info!("finished id={:?}", instance.id); + + match instance_json_file_write(&instance) { + Ok(dest) => info!("wrote dest={:?}", dest), + Err(e) => error!("json write error={:?}", e), + }; + } + + return Ok(instance); +} + +fn instance_json_file_write(g: &Instance) -> Result { + let dest = format!("/var/lib/mnml/data/instances/{}.mnml.instance.json", g.id); + serde_json::to_writer(File::create(&dest)?, g)?; + Ok(dest) +} + +pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result { + let query = " + SELECT * + FROM instances + WHERE id = $1 + FOR UPDATE; + "; + + let result = tx + .query(query, &[&instance_id])?; + + let returned = match result.iter().next() { + Some(row) => row, + None => return Err(err_msg("instance not found")), + }; + + let instance_bytes: Vec = returned.get("data"); + let instance = from_slice::(&instance_bytes)?; + + return Ok(instance); +} + +pub fn instance_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { + let query = " + DELETE + FROM instances + WHERE id = $1; + "; + + let result = tx + .execute(query, &[&id])?; + + if result != 1 { + return Err(format_err!("unable to delete instance {:?}", id)); + } + + info!("instance deleted {:?}", id); + + return Ok(()); +} + +pub fn _instance_list(tx: &mut Transaction) -> Result, Error> { + let query = " + SELECT data, id + FROM instances + AND finished = false; + "; + + let result = tx + .query(query, &[])?; + + let mut list = vec![]; + + for row in result.into_iter() { + let bytes: Vec = row.get(0); + let id = row.get(1); + + match from_slice::(&bytes) { + Ok(i) => list.push(i), + Err(_e) => { + instance_delete(tx, id)?; + } + }; + } + + return Ok(list); +} + +pub fn instances_need_upkeep(tx: &mut Transaction) -> Result, Error> { + let query = " + SELECT data, id + FROM instances + WHERE finished = false + AND upkeep < now() + FOR UPDATE; + "; + + let result = tx + .query(query, &[])?; + + let mut list = vec![]; + + for row in result.into_iter() { + let bytes: Vec = row.get(0); + let id = row.get(1); + + match from_slice::(&bytes) { + Ok(i) => list.push(i), + Err(_e) => { + instance_delete(tx, id)?; + } + }; + } + + return Ok(list); +} + +// timed out instances with no time control +pub fn instances_idle(tx: &mut Transaction) -> Result, Error> { + let query = " + SELECT data, id + FROM instances + WHERE finished = false + AND updated_at < now() - interval '1 hour' + FOR UPDATE; + "; + + let result = tx + .query(query, &[])?; + + let mut list = vec![]; + + for row in result.into_iter() { + let bytes: Vec = row.get(0); + let id = row.get(1); + + match from_slice::(&bytes) { + Ok(i) => list.push(i), + Err(_e) => { + instance_delete(tx, id)?; + } + }; + } + + return Ok(list); +} + + +pub fn instance_practice(tx: &mut Transaction, account: &Account) -> Result { + let bot = bot_player(); + let bot_id = bot.id; + + // generate bot imgs for the client to see + for c in bot.constructs.iter() { + img::shapes_write(c.img)?; + } + + let mut instance = Instance::new() + .set_time_control(TimeControl::Practice) + .set_name(bot.name.clone())?; + + let player = account.to_player(tx)?; + + instance.add_player(player.clone())?; + instance.add_player(bot)?; + + instance.player_ready(bot_id)?; + // skip faceoff + instance.player_ready(player.id)?; + + instance = instance_create(tx, instance)?; + player_create(tx, player, instance.id, account)?; + + Ok(instance) +} + +pub fn pvp(tx: &mut Transaction, a: &Account, b: &Account) -> Result { + let mut instance = Instance::new() + // TODO generate nice game names + .set_name("PVP".to_string())?; + + instance = instance_create(tx, instance)?; + + for account in [a, b].iter() { + let acc_p = account.to_player(tx)?; + let player = player_create(tx, acc_p, instance.id, account)?; + instance.add_player(player)?; + } + + instance_update(tx, instance) +} + +pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result { + let query = " + INSERT INTO players (id, instance, account) + VALUES ($1, $2, $3) + RETURNING id, account; + "; + + let result = tx + .query(query, &[&Uuid::new_v4(), &instance, &account.id])?; + + let _returned = result.iter().next().expect("no row written"); + + info!("wrote player {:} joined instance: {:}", account.name, instance); + + return Ok(player); +} + +pub fn instance_abandon(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { + let mut instance = instance_get(tx, instance_id)?; + + if let Some(game_id) = instance.current_game_id() { + let mut game = game_get(tx, game_id)?; + game.player_by_id(account.id)?.forfeit(); + game = game.start(); // actually finishes it... + game_update(tx, &game)?; + } + + instance.account_player(account.id)?.set_lose(); + instance.account_opponent(account.id)?.set_win(); + instance.next_round(); + + Ok(RpcMessage::InstanceState(instance_update(tx, instance)?)) +} + +pub fn instance_ready(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { + let mut instance = instance_get(tx, instance_id)?; + let player_id = instance.account_player(account.id)?.id; + + if let Some(game) = instance.player_ready(player_id)? { + game_write(tx, &game)?; + + // ensures cleanup for warden etc is done + game_update(tx, &game)?; + + instance_update(tx, instance)?; + return Ok(RpcMessage::GameState(game)); + } + + Ok(RpcMessage::InstanceState(instance_update(tx, instance)?)) +} + +pub fn instance_state(tx: &mut Transaction, instance_id: Uuid) -> Result { + let instance = instance_get(tx, instance_id)?; + + if let Some(game_id) = instance.current_game_id() { + let game = game_get(tx, game_id)?; + + // return the game until it's finished + if game.phase != Phase::Finished { + return Ok(RpcMessage::GameState(game)) + } + } + + Ok(RpcMessage::InstanceState(instance)) +} + +pub fn instance_game_finished(tx: &mut Transaction, game: &Game, instance_id: Uuid) -> Result<(), Error> { + let mut instance = instance_get(tx, instance_id)?; + instance.game_finished(game)?; + // info!("{:?}", instance_get(tx, instance_id)?); + + instance_update(tx, instance)?; + + Ok(()) +} + +pub fn bot_instance() -> Instance { + let mut instance = Instance::new(); + + let bot_player = bot_player(); + let bot = bot_player.id; + instance.add_player(bot_player).unwrap(); + + let player_account = Uuid::new_v4(); + let constructs = instance_mobs(player_account); + let player = Player::new(player_account, None, &"test".to_string(), constructs).set_bot(true); + + instance.add_player(player).expect("could not add player"); + instance.player_ready(player_account).unwrap(); + instance.player_ready(bot).unwrap(); + + return instance; +} + +pub fn demo() -> Result, Error> { + let bot = bot_player(); + + // generate bot imgs for the client to see + for c in bot.constructs.iter() { + img::shapes_write(c.img)?; + }; + + let bot2 = bot_player(); + + // generate bot imgs for the client to see + for c in bot2.constructs.iter() { + img::shapes_write(c.img)?; + }; + + + Ok(vec![bot, bot2]) +} + + +pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { + let instance = instance_get(tx, instance_id)? + .vbox_refill(account.id)?; + return instance_update(tx, instance); +} + +pub fn vbox_buy(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: ItemType, index: String, construct_id: Option) -> Result { + let instance = instance_get(tx, instance_id)? + .vbox_buy(account.id, group, index, construct_id)?; + return instance_update(tx, instance); +} + +pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, stash_indices: Vec, vbox_indices: VboxIndices) -> Result { + let instance = instance_get(tx, instance_id)? + .vbox_combine(account.id, stash_indices, vbox_indices)?; + return instance_update(tx, instance); +} + +pub fn vbox_refund(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: String) -> Result { + let instance = instance_get(tx, instance_id)? + .vbox_refund(account.id, index)?; + return instance_update(tx, instance); +} + +pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: String) -> Result { + let instance = instance_get(tx, instance_id)? + .vbox_apply(account.id, index, construct_id)?; + return instance_update(tx, instance); +} + +pub fn vbox_unequip(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Option) -> Result { + let instance = instance_get(tx, instance_id)? + .vbox_unequip(account.id, target, construct_id, target_construct_id)?; + return instance_update(tx, instance); +} \ No newline at end of file diff --git a/server/src/player.rs b/server/src/player.rs deleted file mode 100644 index 612e1ce8..00000000 --- a/server/src/player.rs +++ /dev/null @@ -1,490 +0,0 @@ -use std::collections::{HashMap}; - -use uuid::Uuid; -use rand::prelude::*; - -use postgres::transaction::Transaction; - -use failure::Error; -use failure::err_msg; - -use account; -use account::Account; -use construct::{Construct, Colours}; -use vbox::{Vbox, ItemType, VboxIndices}; -use item::{Item, ItemEffect}; -use effect::{Effect}; - -const DISCARD_COST: usize = 2; - -#[derive(Debug,Copy,Clone,Serialize,Deserialize,Eq,PartialEq)] -pub enum Score { - Zero, - One, - Two, - Three, - Adv, - - Win, - Lose, -} - -impl Score { - pub fn add_win(self, _opp: &Score) -> Score { - match self { - Score::Zero => Score::One, - Score::One => Score::Two, - Score::Two => Score::Win, - // Tennis scoring - // Score::Three => match opp { - // Score::Adv => Score::Three, - // Score::Three => Score::Adv, - // _ => Score::Win, - // } - // Score::Adv => Score::Win, - - _ => panic!("faulty score increment {:?}", self), - } - } - - pub fn add_loss(self) -> Score { - match self { - // Score::Adv => Score::Three, - _ => self, - } - } - -} - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct Player { - pub id: Uuid, - pub img: Option, - pub name: String, - pub vbox: Vbox, - pub constructs: Vec, - pub bot: bool, - pub ready: bool, - pub draw_offered: bool, - pub score: Score, -} - -impl Player { - pub fn from_account(tx: &mut Transaction, account: &Account) -> Result { - let constructs = account::team(tx, account)?; - - let img = match account.subscribed { - true => Some(account.img), - false => None, - }; - - Ok(Player { - id: account.id, - img, - name: account.name.clone(), - vbox: Vbox::new(), - constructs, - bot: false, - ready: false, - draw_offered: false, - score: Score::Zero, - }) - } - - pub fn new(account: Uuid, name: &String, constructs: Vec) -> Player { - Player { - id: account, - img: Some(account), - name: name.clone(), - vbox: Vbox::new(), - constructs, - bot: false, - ready: false, - draw_offered: false, - score: Score::Zero, - } - } - - pub fn redact(mut self, account: Uuid) -> Player { - // all g - if account == self.id { - return self; - } - - // remove vbox - self.vbox = Vbox::new(); - - // hide skills - for construct in self.constructs.iter_mut() { - construct.skills = vec![]; - construct.specs = vec![]; - } - - self - } - - pub fn set_bot(mut self, bot: bool) -> Player { - self.bot = bot; - self - } - - pub fn set_ready(&mut self, ready: bool) -> &mut Player { - self.ready = ready; - self - } - - pub fn forfeit(&mut self) -> &mut Player { - for construct in self.constructs.iter_mut() { - construct.force_ko(); - } - self - } - - pub fn set_win(&mut self) -> &mut Player { - self.score = Score::Win; - self - } - - pub fn set_lose(&mut self) -> &mut Player { - self.score = Score::Lose; - self - } - - pub fn construct_get(&mut self, id: Uuid) -> Result<&mut Construct, Error> { - self.constructs.iter_mut().find(|c| c.id == id).ok_or(err_msg("construct not found")) - } - - pub fn autobuy(&mut self) -> &mut Player { - let mut rng = thread_rng(); - - // skill buying phase - while self.constructs.iter().any(|c| c.skills.len() < 3) { - // find the construct with the smallest number of skills - let construct_id = match self.constructs.iter().min_by_key(|c| c.skills.len()) { - None => panic!("no constructs in autobuy"), - Some(c) => c.id, - }; - - let i = self.vbox.stash.iter() - .find(|(_i, v)| v.into_skill().is_some()) - .map(|(i, _v)| i.clone()); - - // got a skill in stash - if let Some(i) = i { - // AAAAAAAAAAAAAAAAAAAA - // there's a bad bug here where if this apply fails - // the item in question will be silently dropped - let item = self.vbox.stash.remove(&i).unwrap(); - self.vbox_apply(item, construct_id).ok(); - continue; - } - // need to buy one - else { - - // do we have any colours in store? - let colours = self.vbox.store[&ItemType::Colours].keys() - .cloned() - .take(2) - .collect::>(); - - // how about a base skill? - let base = match self.vbox.store[&ItemType::Skills].iter().next() { - Some(b) => Some(b.0.clone()), - None => None, - }; - - // if no: try to refill and start again - match colours.len() != 2 || base.is_none() { - true => { - match self.vbox_refill() { - Ok(_) => continue, - Err(_) => break, // give up - }; - } - false => { - let mut vbox_items = HashMap::new(); - vbox_items.insert(ItemType::Colours, colours); - vbox_items.insert(ItemType::Skills, vec![base.unwrap()]); - - match self.vbox_combine(vec![], Some(vbox_items)) { - Ok(_) => continue, - Err(_) => break, // give up - } - } - } - } - } - - // spec buying phase - while self.constructs.iter().any(|c| c.specs.len() < 3) { - // find the construct with the smallest number of skills - let construct_id = match self.constructs.iter().min_by_key(|c| c.specs.len()) { - None => panic!("no constructs in autobuy"), - Some(c) => c.id, - }; - - let i = self.vbox.stash.iter() - .find(|(_i, v)| v.into_spec().is_some()) - .map(|(i, _v)| i.clone()); - - // got a skill in stash - if let Some(i) = i { - // AAAAAAAAAAAAAAAAAAAA - // there's a bad bug here where if this apply fails - // the item in question will be silently dropped - let item = self.vbox.stash.remove(&i).unwrap(); - self.vbox_apply(item, construct_id).unwrap(); - continue; - } - // need to buy one - else { - // do we have any colours in store? - let colours = self.vbox.store[&ItemType::Colours].keys() - .cloned() - .take(2) - .collect::>(); - - // how about a base spec? - let base = match self.vbox.store[&ItemType::Specs].iter().next() { - Some(b) => Some(b.0.clone()), - None => None, - }; - - // if no: try to refill and start again - match colours.len() != 2 || base.is_none() { - true => match self.vbox_refill() { - Ok(_) => continue, - Err(_) => break, // give up - }, - false => { - let mut vbox_items = HashMap::new(); - vbox_items.insert(ItemType::Colours, colours); - vbox_items.insert(ItemType::Specs, vec![base.unwrap()]); - - match self.vbox_combine(vec![], Some(vbox_items)) { - Ok(_) => continue, - Err(_) => break, // give up - } - } - } - } - } - - // upgrading phase - // NYI - - return self; - } - - pub fn vbox_refill(&mut self) -> Result<&mut Player, Error> { - self.vbox.balance_sub(DISCARD_COST)?; - self.vbox.fill(); - Ok(self) - } - - pub fn bot_vbox_accept(&mut self, group: ItemType) -> Result<&mut Player, Error> { - let item = self.vbox.bot_buy(group)?; - self.vbox.stash_add(item, None)?; - Ok(self) - } - - pub fn vbox_buy(&mut self, group: ItemType, index: String, construct_id: Option) -> Result<&mut Player, Error> { - let item = self.vbox.buy(group, &index)?; - - match construct_id { - Some(id) => { self.vbox_apply(item, id)?; }, - None => { self.vbox.stash_add(item, None)?; }, - }; - - Ok(self) - } - - pub fn vbox_combine(&mut self, inv_indices: Vec, vbox_indices: VboxIndices) -> Result<&mut Player, Error> { - self.vbox.combine(inv_indices, vbox_indices)?; - Ok(self) - } - - pub fn vbox_refund(&mut self, index: String) -> Result<&mut Player, Error> { - self.vbox.refund(index)?; - Ok(self) - } - - pub fn vbox_equip(&mut self, index: String, construct_id: Uuid) -> Result<&mut Player, Error> { - let item = self.vbox.stash.remove(&index) - .ok_or(format_err!("no item at index {:?} {:}", self, &index))?; - - self.vbox_apply(item, construct_id) - } - - pub fn vbox_apply(&mut self, item: Item, construct_id: Uuid) -> Result<&mut Player, Error> { - match item.effect() { - Some(ItemEffect::Skill) => { - let skill = item.into_skill().ok_or(format_err!("item {:?} has no associated skill", item))?; - let construct = self.construct_get(construct_id)?; - // done here because i teach them a tonne of skills for tests - let max_skills = 3; - if construct.skills.len() >= max_skills { - return Err(format_err!("construct at max skills ({:?})", max_skills)); - } - - if construct.knows(skill) { - return Err(format_err!("construct already knows skill ({:?})" , skill)); - } - - construct.learn_mut(skill); - }, - Some(ItemEffect::Spec) => { - let spec = item.into_spec().ok_or(format_err!("item {:?} has no associated spec", item))?; - let construct = self.construct_get(construct_id)?; - construct.spec_add(spec)?; - - }, - None => return Err(err_msg("item has no effect on constructs")), - } - - // now the item has been applied - // recalculate the stats of the whole player - let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| { - Colours { - red: tc.red + c.colours.red, - green: tc.green + c.colours.green, - blue: tc.blue + c.colours.blue - } - }); - - for construct in self.constructs.iter_mut() { - construct.apply_modifiers(&player_colours); - } - - Ok(self) - } - - pub fn vbox_unequip(&mut self, target: Item, construct_id: Uuid, target_construct_id: Option) -> Result<&mut Player, Error> { - if self.vbox.stash.len() >= 9 && !target_construct_id.is_some() { - return Err(err_msg("too many items stash")); - } - - match target.effect() { - Some(ItemEffect::Skill) => { - let skill = target.into_skill().ok_or(format_err!("item {:?} has no associated skill", target))?; - let construct = self.construct_get(construct_id)?; - construct.forget(skill)?; - }, - Some(ItemEffect::Spec) => { - let spec = target.into_spec().ok_or(format_err!("item {:?} has no associated spec", target))?; - let construct = self.construct_get(construct_id)?; - construct.spec_remove(spec)?; - }, - None => return Err(err_msg("item has no effect on constructs")), - } - - // now the item has been applied - // recalculate the stats of the whole player - let player_colours = self.constructs.iter().fold(Colours::new(), |tc, c| { - Colours { - red: tc.red + c.colours.red, - green: tc.green + c.colours.green, - blue: tc.blue + c.colours.blue - } - }); - - for construct in self.constructs.iter_mut() { - construct.apply_modifiers(&player_colours); - } - - match target_construct_id { - Some(cid) => { self.vbox_apply(target, cid)?; }, - None => { self.vbox.stash_add(target, None)?; }, - }; - - Ok(self) - } - - // GAME METHODS - pub fn skills_required(&self) -> usize { - let required = self.constructs.iter() - .filter(|c| !c.is_ko()) - .filter(|c| c.available_skills().len() > 0) - .collect::>().len(); - // info!("{:} requires {:} skills this turn", self.id, required); - return required; - } - - pub fn intercepting(&self) -> Option<&Construct> { - self.constructs.iter() - .find(|c| c.affected(Effect::Intercept)) - } - - pub fn construct_by_id(&mut self, id: Uuid) -> Option<&mut Construct> { - self.constructs.iter_mut().find(|c| c.id == id) - } - -} - -pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result { - let query = " - INSERT INTO players (id, instance, account) - VALUES ($1, $2, $3) - RETURNING id, account; - "; - - let result = tx - .query(query, &[&Uuid::new_v4(), &instance, &account.id])?; - - let _returned = result.iter().next().expect("no row written"); - - info!("wrote player {:} joined instance: {:}", account.name, instance); - - return Ok(player); -} - -#[cfg(test)] -mod tests { - use mob::instance_mobs; - use super::*; - - #[test] - fn player_bot_vbox_test() { - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); - - player.vbox.fill(); - player.vbox.bits = 100; - player.autobuy(); - - assert!(player.constructs.iter().all(|c| c.skills.len() > 1)); - assert!(player.constructs.iter().all(|c| c.specs.len() >= 1)); - } - - #[test] - fn player_score_test() { - let player_account = Uuid::new_v4(); - let constructs = instance_mobs(player_account); - let mut player = Player::new(player_account, &"test".to_string(), constructs).set_bot(true); - - player.score = player.score.add_win(&Score::Zero); - player.score = player.score.add_win(&Score::Zero); - player.score = player.score.add_win(&Score::Zero); - assert_eq!(player.score, Score::Win); // 40 / 0 - - // Bo7 tennis scoring - /*assert_eq!(player.score, Score::Three); // 40 / 0 - - player.score = player.score.add_loss(); // adv -> deuce - assert_eq!(player.score, Score::Three); - - player.score = player.score.add_loss(); // adv -> deuce - assert_eq!(player.score, Score::Three); - - player.score = player.score.add_win(&Score::Adv); // opp adv -> stays deuce - assert_eq!(player.score, Score::Three); - - player.score = player.score.add_win(&Score::Three); - assert_eq!(player.score, Score::Adv); - - player.score = player.score.add_win(&Score::Three); - assert_eq!(player.score, Score::Win);*/ - } - -} \ No newline at end of file diff --git a/server/src/rpc.rs b/server/src/rpc.rs index e27a62dd..238b0015 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -1,3 +1,6 @@ +use mnml_core::item::ItemInfoCtr; +use mnml_core::instance::ChatState; +use mnml_core::item::item_info; use std::collections::HashMap; use std::time::{Instant}; use std::thread::{spawn}; @@ -19,23 +22,47 @@ use crossbeam_channel::{unbounded, Sender as CbSender}; use ws::{Builder, CloseCode, Message, Handler, Request, Response, Settings, Sender as WsSender}; use ws::deflate::DeflateHandler; +use pg::{ + demo, + game_concede, + game_offer_draw, + game_ready, + game_skill, + game_skill_clear, + game_state, + instance_abandon, + instance_practice, + instance_ready, + instance_state, + vbox_apply, + vbox_buy, + vbox_combine, + vbox_refill, + vbox_refund, + vbox_unequip, +}; + use account::{Account}; use account; -use construct::{Construct}; use events::{Event}; -use game::{Game, game_state, game_skill, game_skill_clear, game_ready, game_offer_draw, game_concede}; -use instance::{Instance, ChatState, instance_state, instance_practice, instance_ready, instance_abandon, demo}; -use item::{Item, ItemInfoCtr, item_info}; + +use mnml_core::construct::{Construct}; +use mnml_core::game::{Game}; +use mnml_core::player::Player; + +use mnml_core::vbox::{ItemType}; +use mnml_core::item::Item; +use mnml_core::skill::Skill; +// use mnml_core::skill::{dev_resolve, Resolutions}; +use mnml_core::instance::{Instance}; + use mtx; use mail; -use player::{Player}; use payments; use mail::Email; use pg::{Db}; use pg::{PgPool}; -use skill::{Skill, dev_resolve, Resolutions}; -use vbox::{ItemType, vbox_buy, vbox_apply, vbox_refill, vbox_combine, vbox_refund, vbox_unequip}; use http::{AUTH_CLEAR, TOKEN_HEADER}; #[derive(Debug,Clone,Serialize)] @@ -61,7 +88,7 @@ pub enum RpcMessage { Pong(()), - DevResolutions(Resolutions), + // DevResolutions(Resolutions), QueueRequested(()), QueueJoined(()), @@ -144,8 +171,8 @@ impl Connection { match v { RpcRequest::Ping {} => return Ok(RpcMessage::Pong(())), RpcRequest::ItemInfo {} => return Ok(RpcMessage::ItemInfo(item_info())), - RpcRequest::DevResolve {a, b, skill } => - return Ok(RpcMessage::DevResolutions(dev_resolve(a, b, skill))), + // RpcRequest::DevResolve {a, b, skill } => + // return Ok(RpcMessage::DevResolutions(dev_resolve(a, b, skill))), _ => (), }; diff --git a/server/src/skill.rs b/server/src/skill.rs deleted file mode 100644 index a7edd024..00000000 --- a/server/src/skill.rs +++ /dev/null @@ -1,2188 +0,0 @@ -use rand::{thread_rng, Rng}; -use uuid::Uuid; - -use util::{IntPct}; -use construct::{Construct, ConstructEffect, EffectMeta}; -use item::{Item}; - -use game::{Game}; -use effect::{Effect, Colour, Cooldown}; - -pub fn dev_resolve(a_id: Uuid, b_id: Uuid, skill: Skill) -> Resolutions { - let mut resolutions = vec![]; - - let mut a = Construct::new(); - a.id = a_id; - let mut b = Construct::new(); - b.id = b_id; - if skill.aoe() { // Send an aoe skill event for anims - resolutions.push(Resolution::new(&a, &b).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); - } - return resolve_skill(skill, &mut a, &mut b, resolutions); -} - -pub fn resolve(cast: &Cast, game: &mut Game) -> Resolutions { - let mut resolutions = vec![]; - - let skill = cast.skill; - let source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); - let targets = game.get_targets(cast.skill, &source, cast.target_construct_id); - - if skill.aoe() { // Send an aoe skill event for anims - resolutions.push(Resolution::new(&source, - &game.construct_by_id(cast.target_construct_id).unwrap().clone()).event(Event::AoeSkill { skill }).stages(EventStages::StartEnd)); - } - - for target_id in targets { - // we clone the current state of the target and source - // so we can modify them during the resolution - // no more than 1 mutable ref allowed on game - let mut source = game.construct_by_id(cast.source_construct_id).unwrap().clone(); - let mut target = game.construct_by_id(target_id).unwrap().clone(); - - // bail out on ticks that have been removed - if skill.is_tick() && target.effects.iter().find(|ce| match ce.tick { - Some(t) => t.id == cast.id, - None => false, - }).is_none() { - continue; - } - - resolutions = resolve_skill(cast.skill, &mut source, &mut target, resolutions); - - // save the changes to the game - game.update_construct(&mut source); - game.update_construct(&mut target); - - // do additional steps - resolutions = post_resolve(cast.skill, game, resolutions); - } - - return resolutions; -} - -pub fn resolve_skill(skill: Skill, source: &mut Construct, target: &mut Construct, mut resolutions: Vec) -> Resolutions { - if let Some(_disable) = source.disabled(skill) { - // resolutions.push(Resolution::new(source, target).event(Event::Disable { disable, skill }).stages(EventStages::PostOnly)); - return resolutions; - } - - if target.is_ko() { - // resolutions.push(Resolution::new(source, target).event(Event::TargetKo { skill }).stages(EventStages::PostOnly)); - return resolutions; - } - - if target.affected(Effect::Reflect) && skill.colours().contains(&Colour::Blue) && !skill.is_tick() { - // guard against overflow - if source.affected(Effect::Reflect) { - return resolutions; - } - resolutions.push(Resolution::new(source, target).event(Event::Reflection { skill })); - return resolve_skill(skill, &mut source.clone(), source, resolutions); - } - - if source.affected(Effect::Haste) { - match skill { - Skill::Slay | - Skill::SlayPlus | - Skill::SlayPlusPlus | - Skill::Chaos | - Skill::ChaosPlus | - Skill::ChaosPlusPlus | - Skill::Strike | - Skill::StrikePlus | - Skill::StrikePlusPlus => { - let amount = source.speed().pct(Skill::HasteStrike.multiplier()); - target.deal_red_damage(Skill::HasteStrike, amount) - .into_iter() - .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); - }, - _ => (), - } - } - - if source.affected(Effect::Hybrid) { - match skill { - Skill::Blast| - Skill::BlastPlus | - Skill::BlastPlusPlus | - Skill::Chaos | - Skill::ChaosPlus | - Skill::ChaosPlusPlus | - Skill::Siphon | - Skill::SiphonPlus | - Skill::SiphonPlusPlus => { - let amount = source.green_power().pct(Skill::HybridBlast.multiplier()); - target.deal_blue_damage(Skill::HybridBlast, amount) - .into_iter() - .for_each(|e| resolutions.push(Resolution::new(source, target).event(e))); - }, - _ => (), - } - } - - // match self.category() == EffectCategory::Red { - // true => { - // if let Some(evasion) = target.evade(*self) { - // resolutions.push(evasion); - // return Event; - // } - // }, - // false => (), - // } - - resolutions = match skill { - Skill::Amplify| - Skill::AmplifyPlus | - Skill::AmplifyPlusPlus => amplify(source, target, resolutions, skill), - - Skill::Banish| - Skill::BanishPlus | - Skill::BanishPlusPlus => banish(source, target, resolutions, skill), - - Skill::Bash| - Skill::BashPlus | - Skill::BashPlusPlus => bash(source, target, resolutions, skill), - - Skill::Blast| - Skill::BlastPlus | - Skill::BlastPlusPlus => blast(source, target, resolutions, skill), - - Skill::Chaos| - Skill::ChaosPlus | - Skill::ChaosPlusPlus => chaos(source, target, resolutions, skill), - - Skill::Sustain| - Skill::SustainPlus | - Skill::SustainPlusPlus => sustain(source, target, resolutions, skill), - - Skill::Electrify| - Skill::ElectrifyPlus | - Skill::ElectrifyPlusPlus => electrify(source, target, resolutions, skill), - Skill::ElectrocuteTick| - Skill::ElectrocuteTickPlus | - Skill::ElectrocuteTickPlusPlus => electrocute_tick(source, target, resolutions, skill), - - Skill::Curse| - Skill::CursePlus | - Skill::CursePlusPlus => curse(source, target, resolutions, skill), - - Skill::Decay| - Skill::DecayPlus | - Skill::DecayPlusPlus => decay(source, target, resolutions, skill), - Skill::DecayTick| - Skill::DecayTickPlus | - Skill::DecayTickPlusPlus => decay_tick(source, target, resolutions, skill), - - Skill::Haste| - Skill::HastePlus | - Skill::HastePlusPlus => haste(source, target, resolutions, skill), - - Skill::Heal| - Skill::HealPlus | - Skill::HealPlusPlus => heal(source, target, resolutions, skill), - - Skill::Absorb| - Skill::AbsorbPlus | - Skill::AbsorbPlusPlus => absorb(source, target, resolutions, skill), - - Skill::Hybrid| - Skill::HybridPlus | - Skill::HybridPlusPlus => hybrid(source, target, resolutions, skill), - - Skill::Invert| - Skill::InvertPlus | - Skill::InvertPlusPlus => invert(source, target, resolutions, skill), - - Skill::Counter| - Skill::CounterPlus | - Skill::CounterPlusPlus => counter(source, target, resolutions, skill), - - Skill::Purge| - Skill::PurgePlus | - Skill::PurgePlusPlus => purge(source, target, resolutions, skill), - - Skill::Purify| - Skill::PurifyPlus | - Skill::PurifyPlusPlus => purify(source, target, resolutions, skill), - - Skill::Recharge| - Skill::RechargePlus | - Skill::RechargePlusPlus => recharge(source, target, resolutions, skill), - - Skill::Reflect| - Skill::ReflectPlus | - Skill::ReflectPlusPlus => reflect(source, target, resolutions, skill), - - Skill::Ruin| - Skill::RuinPlus | - Skill::RuinPlusPlus => ruin(source, target, resolutions, skill), - - Skill::Link| - Skill::LinkPlus | - Skill::LinkPlusPlus => link(source, target, resolutions, skill), - - Skill::Silence| - Skill::SilencePlus | - Skill::SilencePlusPlus => silence(source, target, resolutions, skill), - - Skill::Siphon| - Skill::SiphonPlus | - Skill::SiphonPlusPlus => siphon(source, target, resolutions, skill), - Skill::SiphonTick| - Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus => siphon_tick(source, target, resolutions, skill), - - Skill::Slay| - Skill::SlayPlus | - Skill::SlayPlusPlus => slay(source, target, resolutions, skill), - - Skill::Sleep| - Skill::SleepPlus | - Skill::SleepPlusPlus => sleep(source, target, resolutions, skill), - - Skill::Restrict| - Skill::RestrictPlus | - Skill::RestrictPlusPlus => restrict(source, target, resolutions, skill), - - Skill::Strike| - Skill::StrikePlus | - Skill::StrikePlusPlus => strike(source, target, resolutions, skill), - - Skill::Intercept| - Skill::InterceptPlus | - Skill::InterceptPlusPlus => intercept(source, target, resolutions, skill), - - Skill::Break| - Skill::BreakPlus | - Skill::BreakPlusPlus => break_(source, target, resolutions, skill), - - Skill::Triage| - Skill::TriagePlus | - Skill::TriagePlusPlus => triage(source, target, resolutions, skill), - - Skill::TriageTick| - Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => triage_tick(source, target, resolutions, skill), - - // Base Skills - Skill::Attack => attack(source, target, resolutions, skill), - Skill::Block => block(source, target, resolutions, skill), - Skill::Buff => buff(source, target, resolutions, skill), - Skill::Debuff => debuff(source, target, resolutions, skill), - Skill::Stun => stun(source, target, resolutions, skill), - - // Triggered - Skill::Electrocute | - Skill::ElectrocutePlus | - Skill::ElectrocutePlusPlus => panic!("should only trigger from electrify hit"), - Skill::HasteStrike => panic!("should only trigger from haste"), - Skill::Absorption| - Skill::AbsorptionPlus | - Skill::AbsorptionPlusPlus => panic!("should only trigger from absorb"), - Skill::HybridBlast => panic!("should only trigger from hybrid"), - Skill::CounterAttack| - Skill::CounterAttackPlus | - Skill::CounterAttackPlusPlus => panic!("should only trigger from counter"), - - - // Not used - }; - - return resolutions; -} - -fn post_resolve(_skill: Skill, game: &mut Game, mut resolutions: Resolutions) -> Resolutions { - for Resolution { source: event_source, target: event_target, event, stages: _ } in resolutions.clone() { - let mut source = game.construct_by_id(event_source.id).unwrap().clone(); - let mut target = game.construct_by_id(event_target.id).unwrap().clone(); - - match event { - Event::Damage { amount, skill, mitigation, colour: c } => { - if target.affected(Effect::Electric) && !skill.is_tick() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Electric).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - // Gurad against reflect overflow - if !(source.affected(Effect::Reflect) && target.affected(Effect::Reflect)) { - // Check reflect don't bother if electrocute is procing on death - if source.affected(Effect::Reflect) && !target.is_ko() { - resolutions.push(Resolution::new(&target, &source) - .event(Event::Reflection { skill: s }).stages(EventStages::EndPost)); - resolutions = electrocute(&mut source, &mut target, resolutions, s); - } else { - resolutions = electrocute(&mut target, &mut source, resolutions, s); - } - } - }, - _ => panic!("no electrify skill"), - }; - } - - if target.affected(Effect::Absorb) && !target.is_ko() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Absorb).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - resolutions = absorption(&mut source, &mut target, resolutions, skill, amount + mitigation, s); - }, - _ => panic!("no absorb skill"), - }; - } - if c == Colour::Red { - if target.affected(Effect::Counter) && !target.is_ko() { - let ConstructEffect { effect: _, duration: _, meta, tick: _ } = target.effects.iter() - .find(|e| e.effect == Effect::Counter).unwrap().clone(); - match meta { - Some(EffectMeta::Skill(s)) => { - resolutions = counter_attack(&mut target, &mut source, resolutions, s); - }, - _ => panic!("no counter skill"), - }; - } - } - - if target.is_ko() && event_target.green == 0 { - // Make sure target ko is from this event - target.effects.clear(); - resolutions.push(Resolution::new(&source, &target).event(Event::Ko()).stages(EventStages::PostOnly)); - } - }, - _ => (), - }; - - - game.update_construct(&mut source); - game.update_construct(&mut target); - }; - - return resolutions; -} - - - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub struct Cast { - pub id: Uuid, - pub source_player_id: Uuid, - pub source_construct_id: Uuid, - pub target_construct_id: Uuid, - pub skill: Skill, - pub speed: u64, -} - -impl Cast { - pub fn new(source_construct_id: Uuid, source_player_id: Uuid, target_construct_id: Uuid, skill: Skill) -> Cast { - return Cast { - id: Uuid::new_v4(), - source_construct_id, - source_player_id, - target_construct_id, - skill, - speed: 0, - }; - } - - pub fn new_tick(source: &mut Construct, target: &mut Construct, skill: Skill) -> Cast { - Cast { - id: Uuid::new_v4(), - source_construct_id: source.id, - source_player_id: source.account, - target_construct_id: target.id, - skill, - speed: source.skill_speed(skill), - } - } - - pub fn used_cooldown(&self) -> bool { - return self.skill.base_cd().is_some(); - } -} - -pub type Disable = Vec; -pub type Immunity = Vec; - -// used to show the progress of a construct -// while the resolutions are animating -#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] -pub struct EventConstruct { - pub id: Uuid, - pub red: u64, - pub green: u64, - pub blue: u64, -} - -#[derive(Debug,Clone,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 -} - -#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] -pub struct Resolution { - pub source: EventConstruct, - pub target: EventConstruct, - pub event: Event, - pub stages: EventStages, -} - -impl Resolution { - fn new(source: &Construct, target: &Construct) -> Resolution { - Resolution { - source: EventConstruct { - id: source.id, - red: source.red_life(), - green: source.green_life(), - blue: source.blue_life(), - }, - target: EventConstruct { - id: target.id, - red: target.red_life(), - green: target.green_life(), - blue: target.blue_life(), - }, - event: Event::Incomplete, - stages: EventStages::AllStages, - } - } - - fn event(mut self, e: Event) -> Resolution { - self.event = e; - self - } - - fn stages(mut self, s: EventStages) -> Resolution { - self.stages = s; - self - } - - pub fn get_delay(self) -> i64 { - let source_duration = 1000; // Time for SOURCE ONLY - let target_delay = 500; // Used for Source + Target - let target_duration = 1500; // Time for TARGET ONLY - let post_skill = 1000; // Time for all POST - let source_and_target_total = target_delay + target_duration; // SOURCE + TARGET time - - match self.stages { - 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,PartialEq,Serialize,Deserialize)] -pub enum Event { - Disable { skill: Skill, disable: Disable }, - Immunity { skill: Skill, immunity: Immunity }, - Damage { skill: Skill, amount: u64, mitigation: u64, colour: Colour }, - Healing { skill: Skill, amount: u64, overhealing: u64 }, - Recharge { skill: Skill, red: u64, blue: u64 }, - Inversion { skill: Skill }, - Reflection { skill: Skill }, - AoeSkill { skill: Skill }, - Skill { skill: Skill }, - Effect { skill: Skill, effect: Effect, duration: u8, construct_effects: Vec }, - Removal { skill: Skill, effect: Option, construct_effects: Vec }, - TargetKo { skill: Skill }, - // skill not necessary but makes it neater as all events are arrays in js - Ko (), - Forfeit (), - Incomplete, - // not used - Evasion { skill: Skill, evasion_rating: u64 }, -} - -pub type Resolutions = Vec; - -#[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] -pub enum Skill { - Attack, - Debuff, - Buff, - Block, // reduce damage - Stun, - - // Boost -- sounds nice - // Evade, // actively evade - // Nightmare, - // Sleep, - - Amplify, - #[serde(rename = "Amplify+")] - AmplifyPlus, - #[serde(rename = "Amplify++")] - AmplifyPlusPlus, - - Absorb, - #[serde(rename = "Absorb+")] - AbsorbPlus, - #[serde(rename = "Absorb++")] - AbsorbPlusPlus, - - Banish, - #[serde(rename = "Banish+")] - BanishPlus, - #[serde(rename = "Banish++")] - BanishPlusPlus, - - Bash, - #[serde(rename = "Bash+")] - BashPlus, - #[serde(rename = "Bash++")] - BashPlusPlus, - - Blast, - #[serde(rename = "Blast+")] - BlastPlus, - #[serde(rename = "Blast++")] - BlastPlusPlus, - - Chaos, - #[serde(rename = "Chaos+")] - ChaosPlus, - #[serde(rename = "Chaos++")] - ChaosPlusPlus, - - Sustain, - #[serde(rename = "Sustain+")] - SustainPlus, - #[serde(rename = "Sustain++")] - SustainPlusPlus, - - Electrify, - #[serde(rename = "Electrify+")] - ElectrifyPlus, - #[serde(rename = "Electrify++")] - ElectrifyPlusPlus, - - Curse, - #[serde(rename = "Curse+")] - CursePlus, - #[serde(rename = "Curse++")] - CursePlusPlus, - - Decay, - #[serde(rename = "Decay+")] - DecayPlus, - #[serde(rename = "Decay++")] - DecayPlusPlus, - - Haste, - #[serde(rename = "Haste+")] - HastePlus, - #[serde(rename = "Haste++")] - HastePlusPlus, - - Heal, - #[serde(rename = "Heal+")] - HealPlus, - #[serde(rename = "Heal++")] - HealPlusPlus, - - Hybrid, - #[serde(rename = "Hybrid+")] - HybridPlus, - #[serde(rename = "Hybrid++")] - HybridPlusPlus, - - Invert, - #[serde(rename = "Invert+")] - InvertPlus, - #[serde(rename = "Invert++")] - InvertPlusPlus, - - Counter, - #[serde(rename = "Counter+")] - CounterPlus, - #[serde(rename = "Counter++")] - CounterPlusPlus, - - Purge, - #[serde(rename = "Purge+")] - PurgePlus, - #[serde(rename = "Purge++")] - PurgePlusPlus, - - Purify, - #[serde(rename = "Purify+")] - PurifyPlus, - #[serde(rename = "Purify++")] - PurifyPlusPlus, - - Reflect, - #[serde(rename = "Reflect+")] - ReflectPlus, - #[serde(rename = "Reflect++")] - ReflectPlusPlus, - - Recharge, - #[serde(rename = "Recharge+")] - RechargePlus, - #[serde(rename = "Recharge++")] - RechargePlusPlus, - - Ruin, - #[serde(rename = "Ruin+")] - RuinPlus, - #[serde(rename = "Ruin++")] - RuinPlusPlus, - - Link, - #[serde(rename = "Link+")] - LinkPlus, - #[serde(rename = "Link++")] - LinkPlusPlus, - - Silence, - #[serde(rename = "Silence+")] - SilencePlus, - #[serde(rename = "Silence++")] - SilencePlusPlus, - - Slay, - #[serde(rename = "Slay+")] - SlayPlus, - #[serde(rename = "Slay++")] - SlayPlusPlus, - - Sleep, - #[serde(rename = "Sleep+")] - SleepPlus, - #[serde(rename = "Sleep++")] - SleepPlusPlus, - - Restrict, - #[serde(rename = "Restrict+")] - RestrictPlus, - #[serde(rename = "Restrict++")] - RestrictPlusPlus, - - Strike, - #[serde(rename = "Strike+")] - StrikePlus, - #[serde(rename = "Strike++")] - StrikePlusPlus, - - Siphon, - #[serde(rename = "Siphon+")] - SiphonPlus, - #[serde(rename = "Siphon++")] - SiphonPlusPlus, - - Intercept, - #[serde(rename = "Intercept+")] - InterceptPlus, - #[serde(rename = "Intercept++")] - InterceptPlusPlus, - - Break, - #[serde(rename = "Break+")] - BreakPlus, - #[serde(rename = "Break++")] - BreakPlusPlus, - - Triage, - #[serde(rename = "Triage+")] - TriagePlus, - #[serde(rename = "Triage++")] - TriagePlusPlus, - - Absorption, - #[serde(rename = "Absorption+")] - AbsorptionPlus, - #[serde(rename = "Absorption++")] - AbsorptionPlusPlus, - - CounterAttack, - #[serde(rename = "CounterAttack+")] - CounterAttackPlus, - #[serde(rename = "CounterAttack++")] - CounterAttackPlusPlus, - - Electrocute, - #[serde(rename = "Electrocute+")] - ElectrocutePlus, - #[serde(rename = "Electrocute++")] - ElectrocutePlusPlus, - ElectrocuteTick, - #[serde(rename = "ElectrocuteTick+")] - ElectrocuteTickPlus, - #[serde(rename = "ElectrocuteTick++")] - ElectrocuteTickPlusPlus, - - DecayTick, // dot - #[serde(rename = "DecayTick+")] - DecayTickPlus, - #[serde(rename = "DecayTick++")] - DecayTickPlusPlus, - - HasteStrike, - HybridBlast, - - SiphonTick, - #[serde(rename = "SiphonTick+")] - SiphonTickPlus, - #[serde(rename = "SiphonTick++")] - SiphonTickPlusPlus, - - TriageTick, - #[serde(rename = "TriageTick+")] - TriageTickPlus, - #[serde(rename = "TriageTick++")] - TriageTickPlusPlus, -} - -impl Skill { - pub fn multiplier(&self) -> u64 { - match self { - // Attack Base - Skill::Attack => 80, // Base - - Skill::Blast => 105, // BB - Skill::BlastPlus => 125, // BB - Skill::BlastPlusPlus => 145, // BB - - Skill::Chaos => 40, // BR - Skill::ChaosPlus => 50, // BR - Skill::ChaosPlusPlus => 65, // BR - - Skill::Heal => 115, //GG - Skill::HealPlus => 135, //GG - Skill::HealPlusPlus => 160, //GG - - Skill::SiphonTick => 25, // GB - Skill::SiphonTickPlus => 27, - Skill::SiphonTickPlusPlus => 30, - - Skill::Slay => 40, // RG - Skill::SlayPlus => 50, - Skill::SlayPlusPlus => 65, - - Skill::Strike => 90, //RR - Skill::StrikePlus => 110, - Skill::StrikePlusPlus => 140, - - // Block Base - Skill::ElectrocuteTick => 80, - Skill::ElectrocuteTickPlus => 90, - Skill::ElectrocuteTickPlusPlus => 100, - - Skill::CounterAttack => 115, - Skill::CounterAttackPlus => 130, - Skill::CounterAttackPlusPlus => 160, - - Skill::Purify => 45, //Green dmg (heal) - Skill::PurifyPlus => 60, - Skill::PurifyPlusPlus => 85, - - Skill::Reflect => 45, //Recharge blue life (heal) - Skill::ReflectPlus => 70, - Skill::ReflectPlusPlus => 100, - - Skill::Recharge => 70, //Recharge red and blue life (heal) - Skill::RechargePlus => 90, - Skill::RechargePlusPlus => 110, - - Skill::Sustain => 110, // Recharge red life (heal) - Skill::SustainPlus => 130, - Skill::SustainPlusPlus => 150, - - // Stun Base - Skill::Sleep => 160, //Green dmg (heal) - Skill::SleepPlus => 200, - Skill::SleepPlusPlus => 240, - - Skill::Banish => 50, //Green dmg (heal) - Skill::BanishPlus => 65, - Skill::BanishPlusPlus => 80, - - Skill::Bash => 45, - Skill::BashPlus => 55, - Skill::BashPlusPlus => 70, - - Skill::Link => 25, - Skill::LinkPlus => 35, - Skill::LinkPlusPlus => 45, - - Skill::Ruin => 40, - Skill::RuinPlus => 55, - Skill::RuinPlusPlus => 70, - - // Debuff Base - Skill::DecayTick => 33, - Skill::DecayTickPlus => 37, - Skill::DecayTickPlusPlus => 45, - - Skill::Silence => 55, // Deals more per blue skill on target - Skill::SilencePlus => 65, - Skill::SilencePlusPlus => 80, - - Skill::Restrict => 40, // Deals more per red skill on target - Skill::RestrictPlus => 55, - Skill::RestrictPlusPlus => 70, - - // Buff base - Skill::HybridBlast => 50, - - Skill::HasteStrike => 60, - - Skill::Absorb=> 95, - Skill::AbsorbPlus => 110, - Skill::AbsorbPlusPlus => 120, - - Skill::Intercept => 85, - Skill::InterceptPlus => 100, - Skill::InterceptPlusPlus => 125, - - Skill::TriageTick => 75, - Skill::TriageTickPlus => 90, - Skill::TriageTickPlusPlus => 110, - - _ => 100, - } - } - - pub fn effect(&self) -> Vec { - match self { - // Modifiers - Skill::Amplify => vec![ConstructEffect {effect: Effect::Amplify, duration: 2, - meta: Some(EffectMeta::Multiplier(150)), tick: None}], - Skill::AmplifyPlus => vec![ConstructEffect {effect: Effect::Amplify, duration: 3, - meta: Some(EffectMeta::Multiplier(175)), tick: None}], - Skill::AmplifyPlusPlus => vec![ConstructEffect {effect: Effect::Amplify, duration: 4, - meta: Some(EffectMeta::Multiplier(200)), tick: None}], - - Skill::Banish => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], - Skill::BanishPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], - Skill::BanishPlusPlus => vec![ConstructEffect {effect: Effect::Banish, duration: 2, meta: None, tick: None}], - Skill::Block => vec![ConstructEffect {effect: Effect::Block, duration: 1, - meta: Some(EffectMeta::Multiplier(35)), tick: None}], - Skill::Buff => vec![ConstructEffect {effect: Effect::Buff, duration: 3, - meta: Some(EffectMeta::Multiplier(130)), tick: None }], - - Skill::Electrify => vec![ConstructEffect {effect: Effect::Electric, duration: 1, - meta: Some(EffectMeta::Skill(Skill::Electrocute)), tick: None}], - Skill::ElectrifyPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 1, - meta: Some(EffectMeta::Skill(Skill::ElectrocutePlus)), tick: None}], - Skill::ElectrifyPlusPlus => vec![ConstructEffect {effect: Effect::Electric, duration: 1, - meta: Some(EffectMeta::Skill(Skill::ElectrocutePlusPlus)), tick: None}], - Skill::Electrocute => vec![ConstructEffect {effect: Effect::Electrocute, duration: 2, - meta: Some(EffectMeta::Skill(Skill::ElectrocuteTick)), tick: None}], - Skill::ElectrocutePlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 3, - meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickPlus)), tick: None}], - Skill::ElectrocutePlusPlus => vec![ConstructEffect {effect: Effect::Electrocute, duration: 4, - meta: Some(EffectMeta::Skill(Skill::ElectrocuteTickPlusPlus)), tick: None}], - - Skill::Sustain => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }], - Skill::SustainPlus => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }], - Skill::SustainPlusPlus => vec![ConstructEffect {effect: Effect::Sustain, duration: 1, meta: None, tick: None }], - - Skill::Curse => vec![ConstructEffect {effect: Effect::Curse, duration: 2, - meta: Some(EffectMeta::Multiplier(150)), tick: None}], - Skill::CursePlus => vec![ConstructEffect {effect: Effect::Curse, duration: 2, - meta: Some(EffectMeta::Multiplier(175)), tick: None}], - Skill::CursePlusPlus => vec![ConstructEffect {effect: Effect::Curse, duration: 3, - meta: Some(EffectMeta::Multiplier(200)), tick: None}], - - Skill::Debuff => vec![ConstructEffect {effect: Effect::Slow, duration: 3, - meta: Some(EffectMeta::Multiplier(50)), tick: None }], - - Skill::Decay => vec![ConstructEffect {effect: Effect::Wither, duration: 3, - meta: Some(EffectMeta::Multiplier(50)), tick: None }, - ConstructEffect {effect: Effect::Decay, duration: 3, - meta: Some(EffectMeta::Skill(Skill::DecayTick)), tick: None}], - Skill::DecayPlus => vec![ConstructEffect {effect: Effect::Wither, duration: 3, - meta: Some(EffectMeta::Multiplier(35)), tick: None }, - ConstructEffect {effect: Effect::Decay, duration: 3, - meta: Some(EffectMeta::Skill(Skill::DecayTickPlus)), tick: None}], - Skill::DecayPlusPlus => vec![ConstructEffect {effect: Effect::Wither, duration: 4, - meta: Some(EffectMeta::Multiplier(20)), tick: None }, - ConstructEffect {effect: Effect::Decay, duration: 4, - meta: Some(EffectMeta::Skill(Skill::DecayTickPlusPlus)), tick: None}], - - Skill::Haste => vec![ConstructEffect {effect: Effect::Haste, duration: 3, - meta: Some(EffectMeta::Multiplier(150)), tick: None }], - Skill::HastePlus => vec![ConstructEffect {effect: Effect::Haste, duration: 4, - meta: Some(EffectMeta::Multiplier(175)), tick: None }], - Skill::HastePlusPlus => vec![ConstructEffect {effect: Effect::Haste, duration: 5, - meta: Some(EffectMeta::Multiplier(225)), tick: None }], - - Skill::Absorb => vec![ConstructEffect {effect: Effect::Absorb, duration: 1, - meta: Some(EffectMeta::Skill(Skill::Absorption)), tick: None}], - Skill::AbsorbPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 1, - meta: Some(EffectMeta::Skill(Skill::AbsorptionPlus)), tick: None}], - Skill::AbsorbPlusPlus => vec![ConstructEffect {effect: Effect::Absorb, duration: 1, - meta: Some(EffectMeta::Skill(Skill::AbsorptionPlusPlus)), tick: None}], - - Skill::Absorption => vec![ConstructEffect {effect: Effect::Absorption, duration: 3, meta: None, tick: None}], - Skill::AbsorptionPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 4, meta: None, tick: None}], - Skill::AbsorptionPlusPlus => vec![ConstructEffect {effect: Effect::Absorption, duration: 5, meta: None, tick: None}], - - Skill::Hybrid => vec![ConstructEffect {effect: Effect::Hybrid, duration: 3, - meta: Some(EffectMeta::Multiplier(150)), tick: None }], - Skill::HybridPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 4, - meta: Some(EffectMeta::Multiplier(175)), tick: None }], - Skill::HybridPlusPlus => vec![ConstructEffect {effect: Effect::Hybrid, duration: 5, - meta: Some(EffectMeta::Multiplier(200)), tick: None }], - - Skill::Invert => vec![ConstructEffect {effect: Effect::Invert, duration: 2, meta: None, tick: None}], - Skill::InvertPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 3, meta: None, tick: None}], - Skill::InvertPlusPlus => vec![ConstructEffect {effect: Effect::Invert, duration: 4, meta: None, tick: None}], - - Skill::Counter => vec![ConstructEffect {effect: Effect::Counter, duration: 1, - meta: Some(EffectMeta::Skill(Skill::CounterAttack)), tick: None}], - Skill::CounterPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, - meta: Some(EffectMeta::Skill(Skill::CounterAttackPlus)), tick: None}], - Skill::CounterPlusPlus => vec![ConstructEffect {effect: Effect::Counter, duration: 1, - meta: Some(EffectMeta::Skill(Skill::CounterAttackPlusPlus)), tick: None}], - - Skill::Reflect => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], - Skill::ReflectPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], - Skill::ReflectPlusPlus => vec![ConstructEffect {effect: Effect::Reflect, duration: 1, meta: None, tick: None }], - - Skill::Break => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}, - ConstructEffect {effect: Effect::Vulnerable, duration: 3, - meta: Some(EffectMeta::Multiplier(150)), tick: None}], - Skill::BreakPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}, - ConstructEffect {effect: Effect::Vulnerable, duration: 4, - meta: Some(EffectMeta::Multiplier(175)), tick: None}], - Skill::BreakPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}, - ConstructEffect {effect: Effect::Vulnerable, duration: 4, - meta: Some(EffectMeta::Multiplier(200)), tick: None}], - - Skill::Ruin => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], - Skill::RuinPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], - Skill::RuinPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], - - Skill::Purge => vec![ConstructEffect {effect: Effect::Purge, duration: 2, meta: None, tick: None}], - Skill::PurgePlus => vec![ConstructEffect {effect: Effect::Purge, duration: 3, meta: None, tick: None}], - Skill::PurgePlusPlus => vec![ConstructEffect {effect: Effect::Purge, duration: 4, meta: None, tick: None}], - - Skill::Link => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], - Skill::LinkPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], - Skill::LinkPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 1, meta: None, tick: None}], - - Skill::Silence => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}], - Skill::SilencePlus => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}], - Skill::SilencePlusPlus => vec![ConstructEffect {effect: Effect::Silence, duration: 2, meta: None, tick: None}], - - Skill::Siphon => vec![ConstructEffect {effect: Effect::Siphon, duration: 2, - meta: Some(EffectMeta::Skill(Skill::SiphonTick)), tick: None}], - Skill::SiphonPlus => vec![ConstructEffect {effect: Effect::Siphon, duration: 3, - meta: Some(EffectMeta::Skill(Skill::SiphonTickPlus)), tick: None}], - Skill::SiphonPlusPlus => vec![ConstructEffect {effect: Effect::Siphon, duration: 4, - meta: Some(EffectMeta::Skill(Skill::SiphonTickPlusPlus)), tick: None}], - - Skill::Sleep => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], - Skill::SleepPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 3, meta: None, tick: None}], - Skill::SleepPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 4, meta: None, tick: None}], - - Skill::Restrict => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}], - Skill::RestrictPlus => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}], - Skill::RestrictPlusPlus => vec![ConstructEffect {effect: Effect::Restrict, duration: 2, meta: None, tick: None}], - - Skill::Bash => vec![ConstructEffect {effect: Effect::Stun, duration: 2, - meta: Some(EffectMeta::Skill(Skill::Bash)), tick: None}], - Skill::BashPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, - meta: Some(EffectMeta::Skill(Skill::BashPlus)), tick: None}], - Skill::BashPlusPlus => vec![ConstructEffect {effect: Effect::Stun, duration: 2, - meta: Some(EffectMeta::Skill(Skill::BashPlusPlus)), tick: None}], - Skill::Stun => vec![ConstructEffect {effect: Effect::Stun, duration: 2, meta: None, tick: None}], - - Skill::Intercept => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}], - Skill::InterceptPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}], - Skill::InterceptPlusPlus => vec![ConstructEffect {effect: Effect::Intercept, duration: 1, meta: None, tick: None}], - - Skill::Triage => vec![ConstructEffect {effect: Effect::Triage, duration: 2, - meta: Some(EffectMeta::Skill(Skill::TriageTick)), tick: None}], - Skill::TriagePlus => vec![ConstructEffect {effect: Effect::Triage, duration: 3, - meta: Some(EffectMeta::Skill(Skill::TriageTickPlus)), tick: None}], - Skill::TriagePlusPlus => vec![ConstructEffect {effect: Effect::Triage, duration: 4, - meta: Some(EffectMeta::Skill(Skill::TriageTickPlusPlus)), tick: None}], - - Skill::Purify => vec![ConstructEffect { effect: Effect::Pure, duration: 2, - meta: Some(EffectMeta::Multiplier(150)), tick: None}], - Skill::PurifyPlus => vec![ConstructEffect { effect: Effect::Pure, duration: 2, - meta: Some(EffectMeta::Multiplier(175)), tick: None}], - Skill::PurifyPlusPlus => vec![ConstructEffect { effect: Effect::Pure, duration: 2, - meta: Some(EffectMeta::Multiplier(200)), tick: None}], - - _ => { - panic!("{:?} no skill effect", self); - }, - } - } - - pub fn base_cd(&self) -> Cooldown { - match self { - Skill::Attack => None, - Skill::Block => None, // reduce damage - Skill::Buff => None, - Skill::Debuff => Some(1), - Skill::Stun => Some(2), - - Skill::Strike=> None, - Skill::StrikePlus => None, - Skill::StrikePlusPlus => None, - - Skill::Counter| - Skill::CounterPlus | - Skill::CounterPlusPlus => None, // avoid all damage - - Skill::Restrict | - Skill::RestrictPlus | - Skill::RestrictPlusPlus => Some(2), - - Skill::Bash | - Skill::BashPlus | - Skill::BashPlusPlus => Some(2), - - Skill::Heal=> None, - Skill::HealPlus => None, - Skill::HealPlusPlus => None, - - Skill::Triage=> None, // hot - Skill::TriagePlus => None, // hot - Skill::TriagePlusPlus => None, // hot - - Skill::Break | // no damage stun, adds vulnerable - Skill::BreakPlus | - Skill::BreakPlusPlus => Some(1), - - Skill::Blast | - Skill::BlastPlus | - Skill::BlastPlusPlus => None, - - Skill::Chaos | - Skill::ChaosPlus | - Skill::ChaosPlusPlus => None, - - Skill::Amplify | - Skill::AmplifyPlus | - Skill::AmplifyPlusPlus => Some(1), - - Skill::Hybrid | - Skill::HybridPlus | - Skill::HybridPlusPlus => Some(1), - - Skill::Invert | - Skill::InvertPlus | - Skill::InvertPlusPlus => Some(2), - - Skill::Decay => None, // dot - Skill::DecayPlus => None, - Skill::DecayPlusPlus => None, - - Skill::Siphon| - Skill::SiphonPlus | - Skill::SiphonPlusPlus => None, - - Skill::Curse | - Skill::CursePlus | - Skill::CursePlusPlus => Some(1), - - Skill::Link | - Skill::LinkPlus | - Skill::LinkPlusPlus => Some(1), - - Skill::Silence | - Skill::SilencePlus | - Skill::SilencePlusPlus => Some(2), - - Skill::Purify | - Skill::PurifyPlus | - Skill::PurifyPlusPlus => None, - - Skill::Purge | - Skill::PurgePlus | - Skill::PurgePlusPlus => Some(1), - - Skill::Banish | - Skill::BanishPlus | - Skill::BanishPlusPlus => Some(1), - - Skill::Haste | - Skill::HastePlus | - Skill::HastePlusPlus => Some(1), - - Skill::Reflect | - Skill::ReflectPlus | - Skill::ReflectPlusPlus => None, - - Skill::Recharge | - Skill::RechargePlus | - Skill::RechargePlusPlus => None, - - Skill::Ruin | - Skill::RuinPlus | - Skill::RuinPlusPlus => Some(2), - - Skill::Slay=> None, - Skill::SlayPlus => None, - Skill::SlayPlusPlus => None, - - Skill::Sleep | - Skill::SleepPlus | - Skill::SleepPlusPlus => Some(2), - - Skill::Sustain | - Skill::SustainPlus | - Skill::SustainPlusPlus => Some(1), - - Skill::Intercept => Some(1), - Skill::InterceptPlus => Some(1), - Skill::InterceptPlusPlus => Some(1), - - Skill::Electrify | - Skill::ElectrifyPlus | - Skill::ElectrifyPlusPlus => None, - - Skill::Absorb | - Skill::AbsorbPlus | - Skill::AbsorbPlusPlus => Some(1), - - //----------- - // Never cast directly - //--------- - // Trigger - Skill::HybridBlast | - Skill::HasteStrike | - Skill::CounterAttack| - Skill::CounterAttackPlus | - Skill::CounterAttackPlusPlus | // counter - Skill::Electrocute| - Skill::ElectrocutePlus | - Skill::ElectrocutePlusPlus | - Skill::Absorption| - Skill::AbsorptionPlus | - Skill::AbsorptionPlusPlus | - // Ticks - Skill::ElectrocuteTick| - Skill::ElectrocuteTickPlus | - Skill::ElectrocuteTickPlusPlus | - Skill::DecayTick| - Skill::DecayTickPlus | - Skill::DecayTickPlusPlus | - Skill::SiphonTick| - Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus | - Skill::TriageTick| - Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => None, - } - } - - pub fn ko_castable(&self) -> bool { - match self { - Skill::ElectrocuteTick | - Skill::ElectrocuteTickPlus | - Skill::ElectrocuteTickPlusPlus | - Skill::DecayTick | - Skill::DecayTickPlus | - Skill::DecayTickPlusPlus | - Skill::SiphonTick | - Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus | - - Skill::TriageTick | - Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => true, - _ => false, - } - } - - pub fn is_tick(&self) -> bool { - match self { - Skill::ElectrocuteTick | - Skill::ElectrocuteTickPlus | - Skill::ElectrocuteTickPlusPlus | - Skill::DecayTick | - Skill::DecayTickPlus | - Skill::DecayTickPlusPlus | - Skill::SiphonTick | - Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus | - Skill::TriageTick | - Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => true, - - _ => false, - } - } - - pub fn speed(&self) -> u64 { - match self { - Skill::SiphonTick | - Skill::SiphonTickPlus | - Skill::SiphonTickPlusPlus => Skill::Siphon.speed(), - - Skill::DecayTick | - Skill::DecayTickPlus | - Skill::DecayTickPlusPlus => Skill::Decay.speed(), - - Skill::TriageTick | - Skill::TriageTickPlus | - Skill::TriageTickPlusPlus => Skill::Triage.speed(), - - Skill::ElectrocuteTick | - Skill::ElectrocuteTickPlus | - Skill::ElectrocuteTickPlusPlus => Skill::Electrify.speed(), - - _ => Item::from(*self).speed(), - } - } - - pub fn aoe(&self) -> bool { - match self { - Skill::Ruin | - Skill::RuinPlus | - Skill::RuinPlusPlus => true, - _ => false, - } - } - - pub fn defensive(&self) -> bool { - match self { - Skill::Amplify| - Skill::AmplifyPlus | - Skill::AmplifyPlusPlus | - Skill::Block | - Skill::Sustain | - Skill::SustainPlus | - Skill::SustainPlusPlus | - Skill::Electrify | - Skill::ElectrifyPlus | - Skill::ElectrifyPlusPlus | - Skill::Haste | - Skill::HastePlus | - Skill::HastePlusPlus | - Skill::Heal | - Skill::HealPlus | - Skill::HealPlusPlus | - Skill::Absorb | - Skill::AbsorbPlus | - Skill::AbsorbPlusPlus | - Skill::Invert | - Skill::InvertPlus | - Skill::InvertPlusPlus | - Skill::Intercept | - Skill::InterceptPlus | - Skill::InterceptPlusPlus | - Skill::Counter | - Skill::CounterPlus | - Skill::CounterPlusPlus | - Skill::Purify | - Skill::PurifyPlus | - Skill::PurifyPlusPlus | - Skill::Recharge | - Skill::RechargePlus | - Skill::RechargePlusPlus | - Skill::Reflect | - Skill::ReflectPlus | - Skill::ReflectPlusPlus | - Skill::Triage | - Skill::TriagePlus | - Skill::TriagePlusPlus => true, - - _ => false, - } - } - - fn components(&self) -> Vec { - let mut components = Item::from(*self).components(); - components.sort_unstable(); - return components; - } - - pub fn colours(&self) -> Vec { - let mut components = self.components(); - let colour_items = [Item::Red, Item::Green, Item::Blue]; - components.dedup(); - return components.iter() - .filter(|i| colour_items.contains(i)) - .map(|i| i.into_colour()) - .collect::>(); - } - - fn base(&self) -> Skill { - let bases = [Item::Attack, Item::Stun, Item::Buff, Item::Debuff, Item::Block]; - match self.components() - .iter() - .find(|i| bases.contains(i)) { - Some(i) => i.into_skill().unwrap(), - None => panic!("{:?} has no base item", self), - } - } -} - -fn attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn strike(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn stun(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - return results; -} - -fn bash(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - if results.iter().any(|r| match r.event { - Event::Effect { effect, skill: effect_skill, duration: _, construct_effects: _ } - => effect == Effect::Stun && skill == effect_skill, - _ => false, - }) { - let mut cds = 0; - for cs in target.skills.iter_mut() { - if cs.skill.base_cd().is_some() { - cs.cd = match cs.cd { - None => Some(1), - Some(i) => Some(i + 1), - }; - - cds += 1; - } - } - - let amount = source.red_power().pct(skill.multiplier().pct(100 + 45u64.saturating_mul(cds))); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - return results; -} - - -fn sleep(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn sustain(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let red_amount = source.red_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, 0) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn intercept(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let intercept = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, intercept))); - - let red_amount = source.red_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, 0) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn break_(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let stun = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, stun))); - let vuln = skill.effect()[1]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, vuln)).stages(EventStages::PostOnly)); - - return results; -} - -fn block(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results; -} - -fn buff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0]))); - return results; -} - -fn counter(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0]))); - - return results; -} - -fn counter_attack(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - - return results; -} - -fn restrict(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - skill.effect().into_iter() - .for_each(|e| (results.push(Resolution::new(source, target).event(target.add_effect(skill, e))))); - - let s_multi = target.skills - .iter() - .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Red) { - true => acc + 35, - false => acc, - }); - - let amount = source.red_power().pct(skill.multiplier()).pct(s_multi); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - - return results; -} - -fn slay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.red_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); - let slay_events = target.deal_red_damage(skill, amount); - - for e in slay_events { - match e { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { - results.push(Resolution::new(source, target).event(e)); - let heal = source.deal_green_damage(skill, amount.pct(50)); - for h in heal { - results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); - }; - }, - _ => results.push(Resolution::new(source, target).event(e)), - } - } - - return results; -} - -fn heal(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - return results; -} - -fn triage(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Triage => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no triage tick skill"), - }; - let triage = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target).event(target.add_effect(skill, triage))); - - match skip_tick { - true => return results, - false => return triage_tick(source, target, results, tick_skill) - } -} - -fn triage_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.green_power().pct(skill.multiplier()); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -fn chaos(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let mut rng = thread_rng(); - let b_rng: u64 = rng.gen_range(100, 130); - let amount = source.blue_power().pct(skill.multiplier()).pct(b_rng); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - let r_rng: u64 = rng.gen_range(100, 130); - let amount = source.red_power().pct(skill.multiplier()).pct(r_rng); - target.deal_red_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - return results; -} - -fn blast(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e))); - return results; -} - -fn amplify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn haste(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn debuff(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn decay(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - - let wither = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, wither))); - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Decay => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[1]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no decay tick skill"), - }; - let decay = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, decay)) - .stages(EventStages::PostOnly)); - - match skip_tick { - true => return results, - false => return decay_tick(source, target, results, tick_skill) - } -} - -fn decay_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -// electrify is the buff effect -// when attacked it runs electrocute and applies a debuff -fn electrify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let electrify = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, electrify))); - return results;; -} - -fn electrocute(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - // Remove electric buff, no need to display if construct is dead - if !source.is_ko() { - let electric = source.effects.iter().position(|e| e.effect == Effect::Electric); - match electric { - Some(eff) => { - let ce = source.effects.remove(eff); - results.push(Resolution::new(source, source) - .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: source.effects.clone() }) - .stages(EventStages::PostOnly)); - } - None => () - } - } - - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no electrocute tick skill"), - }; - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Electrocute => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let electrocute = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, electrocute)) - .stages(EventStages::PostOnly)); - - - match skip_tick { - true => return results, - false => return electrocute_tick(source, target, results, tick_skill) - } -} - -fn electrocute_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost))); - return results; -} - -fn ruin(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - results.push(Resolution::new(source, target) - .event(target.add_effect(skill, skill.effect()[0])) - .stages(EventStages::PostOnly)); - return results;; -} - -fn absorb(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, 0, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - return results;; -} - -fn absorption(source: &mut Construct, target: &mut Construct, mut results: Resolutions, reflect_skill: Skill, amount: u64, skill: Skill) -> Resolutions { - let absorb = skill.effect()[0].set_meta(EffectMeta::AddedDamage(amount)); - - results.push(Resolution::new(source, target) - .event(target.add_effect(reflect_skill, absorb)) - .stages(EventStages::PostOnly)); - - let absorb_index = target.effects.iter().position(|e| e.effect == Effect::Absorb).expect("No absorb"); - let ce = target.effects.remove(absorb_index); - - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: Some(ce.effect), construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - return results;; -} - -fn curse(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn hybrid(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn invert(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - return results;; -} - -fn reflect(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, 0, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results;; -} - -fn recharge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - let red_amount = source.red_power().pct(skill.multiplier()); - let blue_amount = source.blue_power().pct(skill.multiplier()); - target.recharge(skill, red_amount, blue_amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn siphon(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - - let skip_tick = target.effects.iter().any(|e| { - match e.effect { - Effect::Siphon => source.skill_speed(skill) <= e.tick.unwrap().speed, - _ => false, - } - }); - let ConstructEffect { effect, duration, meta, tick: _ } = skill.effect()[0]; - let tick_skill = match meta { - Some(EffectMeta::Skill(s)) => s, - _ => panic!("no siphon tick skill"), - }; - let siphon = ConstructEffect::new(effect, duration).set_tick(Cast::new_tick(source, target, tick_skill)); - results.push(Resolution::new(source, target).event(target.add_effect(skill, siphon))); - - match skip_tick { - true => return results, - false => return siphon_tick(source, target, results, tick_skill) - } -} - -fn siphon_tick(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - let amount = source.blue_power().pct(skill.multiplier()) + source.green_power().pct(skill.multiplier()); - let siphon_events = target.deal_blue_damage(skill, amount); - - for e in siphon_events { - match e { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => { - results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)); - let heal = source.deal_green_damage(skill, amount); - for h in heal { - results.push(Resolution::new(source, source).event(h).stages(EventStages::PostOnly)); - }; - }, - _ => results.push(Resolution::new(source, target).event(e).stages(EventStages::EndPost)), - } - } - - return results; -} - -fn link(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let amount = source.blue_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn silence(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0]))); - - let s_multi = target.skills - .iter() - .fold(100, |acc, cs| match cs.skill.colours().contains(&Colour::Blue) { - true => acc + 45, - false => acc, - }); - - let amount = source.blue_power().pct(skill.multiplier()).pct(s_multi); - target.deal_blue_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - - return results; -} - -fn purge(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - if target.effects.len() > 0 { - target.effects.clear(); - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - } - - let effect = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); - - return results; -} - -fn purify(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - if target.effects.len() > 0 { - let amount = source.green_power().pct(skill.multiplier().saturating_mul(target.effects.len() as u64)); - target.effects.clear(); - results.push(Resolution::new(source, target) - .event(Event::Removal { skill, effect: None, construct_effects: target.effects.clone() }) - .stages(EventStages::PostOnly)); - target.deal_green_damage(skill, amount) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - let effect = skill.effect()[0]; - results.push(Resolution::new(source, target).event(target.add_effect(skill, effect)).stages(EventStages::PostOnly)); - - return results; -} - -fn banish(source: &mut Construct, target: &mut Construct, mut results: Resolutions, skill: Skill) -> Resolutions { - results.push(Resolution::new(source, target).event(Event::Skill { skill }).stages(EventStages::StartEnd)); - - let red_damage = target.red_life().pct(skill.multiplier()); - let blue_damage = target.blue_life().pct(skill.multiplier()); - - if red_damage > 0 { - target.deal_red_damage(skill, red_damage) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - if blue_damage > 0 { - target.deal_blue_damage(skill, blue_damage) - .into_iter() - .for_each(|e| results.push(Resolution::new(source, target).event(e).stages(EventStages::PostOnly))); - } - - results.push(Resolution::new(source, target).event(target.add_effect(skill, skill.effect()[0])).stages(EventStages::PostOnly)); - return results; -} - -#[cfg(test)] -mod tests { - use skill::*; - - #[test] - fn heal_test() { - let mut x = Construct::new() - .named(&"muji".to_string()) - .learn(Skill::Heal); - - let mut y = Construct::new() - .named(&"camel".to_string()) - .learn(Skill::Heal); - - x.deal_red_damage(Skill::Attack, 5); - - heal(&mut y, &mut x, vec![], Skill::Heal); - } - - #[test] - fn decay_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - decay(&mut x, &mut y, vec![], Skill::Decay); - - assert!(y.effects.iter().any(|e| e.effect == Effect::Decay)); - - y.reduce_effect_durations(); - let _decay = y.effects.iter().find(|e| e.effect == Effect::Decay); - // assert!(y.green_life() == y.green_life().saturating_sub(decay.unwrap().tick.unwrap().amount)); - } - - #[test] - fn block_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - // ensure it doesn't have 0 pd - x.red_power.force(100); - y.green_life.force(500); - - block(&mut y.clone(), &mut y, vec![], Skill::Block); - assert!(y.effects.iter().any(|e| e.effect == Effect::Block)); - - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => - assert!(amount < x.red_power().pct(Skill::Attack.multiplier())), - _ => panic!("not damage"), - }; - } - - #[test] - fn sustain_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - x.red_power.force(10000000000000); // multiplication of int max will cause overflow - y.green_life.force(1024); // make tests more flexible if we change stats - - sustain(&mut y.clone(), &mut y, vec![], Skill::Sustain); - assert!(y.affected(Effect::Sustain)); - - let mut results = ruin(&mut x, &mut y, vec![], Skill::Ruin); - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Immunity { skill: _, immunity } => assert!(immunity.contains(&Effect::Sustain)), - _ => panic!("not immune cluthc"), - }; - - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - assert!(y.green_life() == 1); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert_eq!(amount, 1023), - _ => panic!("not damage"), - }; - } - - #[test] - fn invert_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - // give red shield but reduce to 0 - y.red_life.force(64); - y.red_life.reduce(64); - x.red_power.force(512); - invert(&mut y.clone(), &mut y, vec![], Skill::Invert); - assert!(y.affected(Effect::Invert)); - - // heal should deal green damage - heal(&mut x, &mut y, vec![], Skill::Heal); - assert!(y.green_life() < 1024); - - // attack should heal and recharge red shield - let mut results = attack(&mut x, &mut y, vec![], Skill::Attack); - - // match results.remove(0).event { - // Event::Inversion { skill } => assert_eq!(skill, Skill::Attack), - // _ => panic!("not inversion"), - //}; - - match results.remove(0).event { - Event::Healing { skill: _, overhealing: _, amount } => assert!(amount > 0), - _ => panic!("not healing from inversion"), - }; - - match results.remove(0).event { - Event::Recharge { skill: _, red, blue: _ } => assert!(red > 0), - _ => panic!("not recharge from inversion"), - }; - } - - #[test] - fn reflect_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - reflect(&mut y.clone(), &mut y, vec![], Skill::Reflect); - assert!(y.affected(Effect::Reflect)); - - let mut results = vec![]; - results = resolve_skill(Skill::Blast, &mut x, &mut y, results); - - assert!(x.green_life() < 1024); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Reflection { skill } => assert_eq!(skill, Skill::Blast), - _ => panic!("not reflection"), - }; - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, mitigation: _, colour: _, skill: _ } => assert!(amount > 0), - _ => panic!("not damage"), - }; - } - - #[test] - fn siphon_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"camel".to_string()); - - x.blue_power.force(256); - x.green_power.force(220); - x.green_life.force(1024); - y.blue_life.force(0); - x.green_life.reduce(512); - - let mut results = resolve_skill(Skill::Siphon, &mut x, &mut y, vec![]); - - assert!(y.affected(Effect::Siphon)); - assert!(x.green_life() == (512 + 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier()))); - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Effect { effect, skill: _, duration: _, construct_effects: _ } => assert_eq!(effect, Effect::Siphon), - _ => panic!("not siphon"), - }; - - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Damage { amount, skill: _, mitigation: _, colour: _} => assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) - + 220.pct(Skill::SiphonTick.multiplier())), - _ => panic!("not damage siphon"), - }; - - let Resolution { source: _, target, event, stages: _ } = results.remove(0); - match event { - Event::Healing { amount, skill: _, overhealing: _ } => { - assert_eq!(amount, 256.pct(Skill::SiphonTick.multiplier()) + 220.pct(Skill::SiphonTick.multiplier())); - assert_eq!(target.id, x.id); - }, - _ => panic!("not healing"), - }; - } - - #[test] - fn triage_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()); - - // ensure it doesn't have 0 sd - x.blue_power.force(50); - - // remove all mitigation - y.red_life.force(0); - y.blue_life.force(0); - - y.deal_red_damage(Skill::Attack, 5); - let prev_hp = y.green_life(); - - triage(&mut x, &mut y, vec![], Skill::Triage); - - assert!(y.effects.iter().any(|e| e.effect == Effect::Triage)); - assert!(y.green_life() > prev_hp); - } - - #[test] - fn recharge_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()); - - y.red_life.force(50); - y.blue_life.force(50); - - y.deal_red_damage(Skill::Attack, 5); - y.deal_blue_damage(Skill::Blast, 5); - - let mut results = recharge(&mut x, &mut y, vec![], Skill::Recharge); - - results.remove(0); - let Resolution { source: _, target: _, event, stages: _ } = results.remove(0); - match event { - Event::Recharge { red, blue, skill: _ } => { - assert!(red == 5); - assert!(blue == 5); - } - _ => panic!("result was not recharge"), - } - } - - - #[test] - fn silence_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - silence(&mut x.clone(), &mut x, vec![], Skill::Silence); - assert!(x.effects.iter().any(|e| e.effect == Effect::Silence)); - assert!(x.disabled(Skill::Silence).is_some()); - } - - #[test] - fn amplify_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - x.blue_power.force(50); - - amplify(&mut x.clone(), &mut x, vec![], Skill::Amplify); - assert!(x.effects.iter().any(|e| e.effect == Effect::Amplify)); - assert_eq!(x.blue_power(), 75); - } - - #[test] - fn purify_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - decay(&mut x.clone(), &mut x, vec![], Skill::Decay); - assert!(x.effects.iter().any(|e| e.effect == Effect::Decay)); - - purify(&mut x.clone(), &mut x, vec![], Skill::Purify); - assert!(!x.effects.iter().any(|e| e.effect == Effect::Decay)); - } - - #[test] - fn bash_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()) - .learn(Skill::Stun); - - let stun_cd = y.skills.iter().find(|cs| cs.skill == Skill::Stun).unwrap().cd.unwrap(); - - bash(&mut x, &mut y, vec![], Skill::Bash); - assert!(!x.effects.iter().any(|e| e.effect == Effect::Stun)); - assert!(y.skills.iter().any(|cs| cs.skill == Skill::Stun && cs.cd.unwrap() == stun_cd + 1)); - } - - #[test] - fn purge_test() { - let mut x = Construct::new() - .named(&"muji".to_string()); - - let mut y = Construct::new() - .named(&"pretaliation".to_string()) - .learn(Skill::Heal) - .learn(Skill::HealPlus); - - purge(&mut x, &mut y, vec![], Skill::Purge); - // 2 turns at lvl 1 - assert!(y.effects.iter().any(|e| e.effect == Effect::Purge && e.duration == 2)); - assert!(y.disabled(Skill::Heal).is_some()); - } -} diff --git a/server/src/spec.rs b/server/src/spec.rs deleted file mode 100644 index 9e084842..00000000 --- a/server/src/spec.rs +++ /dev/null @@ -1,734 +0,0 @@ -use construct::{Stat, Colours}; -use util::{IntPct}; - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct SpecBonus { - pub req: Colours, - pub bonus: u64, -} - -impl SpecBonus { - pub fn get_bonus(&self, c: &Colours) -> u64 { - if c.red >= self.req.red && c.blue >= self.req.blue && c.green >= self.req.green { - return self.bonus; - } - return 0; - } -} - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct SpecValues { - pub base: u64, - pub bonuses: Vec, -} - -impl SpecValues { - pub fn max_value (&self, c: &Colours) -> u64 { - self.bonuses.iter().fold(self.base, |acc, s| acc + s.get_bonus(c)) - } - - pub fn base (self) -> u64 { - self.base - } -} - - -#[derive(Debug,Copy,Clone,Serialize,Deserialize,PartialEq,PartialOrd,Ord,Eq)] -pub enum Spec { - Speed, - SpeedRR, - SpeedBB, - SpeedGG, - SpeedRG, - SpeedGB, - SpeedRB, - - SpeedRRPlus, - SpeedBBPlus, - SpeedGGPlus, - SpeedRGPlus, - SpeedGBPlus, - SpeedRBPlus, - - SpeedRRPlusPlus, - SpeedBBPlusPlus, - SpeedGGPlusPlus, - SpeedRGPlusPlus, - SpeedGBPlusPlus, - SpeedRBPlusPlus, - - Life, - LifeGG, - LifeRR, - LifeBB, - LifeRG, - LifeGB, - LifeRB, - LifeGGPlus, - LifeRRPlus, - LifeBBPlus, - LifeRGPlus, - LifeGBPlus, - LifeRBPlus, - LifeGGPlusPlus, - LifeRRPlusPlus, - LifeBBPlusPlus, - LifeRGPlusPlus, - LifeGBPlusPlus, - LifeRBPlusPlus, - - Power, - PowerRR, - PowerGG, - PowerBB, - PowerRG, - PowerGB, - PowerRB, - PowerRRPlus, - PowerGGPlus, - PowerBBPlus, - PowerRGPlus, - PowerGBPlus, - PowerRBPlus, - PowerRRPlusPlus, - PowerGGPlusPlus, - PowerBBPlusPlus, - PowerRGPlusPlus, - PowerGBPlusPlus, - PowerRBPlusPlus, - -} - -impl Spec { - pub fn affects(&self) -> Vec { - match *self { - Spec::Power => vec![Stat::BluePower, Stat::RedPower, Stat::GreenPower], - Spec::PowerRR => vec![Stat::RedPower], - Spec::PowerGG => vec![Stat::GreenPower], - Spec::PowerBB => vec![Stat::BluePower], - Spec::PowerRG => vec![Stat::GreenPower, Stat::RedPower], - Spec::PowerGB => vec![Stat::GreenPower, Stat::BluePower], - Spec::PowerRB => vec![Stat::RedPower, Stat::BluePower], - Spec::PowerRRPlus => vec![Stat::RedPower], - Spec::PowerGGPlus => vec![Stat::GreenPower], - Spec::PowerBBPlus => vec![Stat::BluePower], - Spec::PowerRGPlus => vec![Stat::GreenPower, Stat::RedPower], - Spec::PowerGBPlus => vec![Stat::GreenPower, Stat::BluePower], - Spec::PowerRBPlus => vec![Stat::RedPower, Stat::BluePower], - Spec::PowerRRPlusPlus => vec![Stat::RedPower], - Spec::PowerGGPlusPlus => vec![Stat::GreenPower], - Spec::PowerBBPlusPlus => vec![Stat::BluePower], - Spec::PowerRGPlusPlus => vec![Stat::GreenPower, Stat::RedPower], - Spec::PowerGBPlusPlus => vec![Stat::GreenPower, Stat::BluePower], - Spec::PowerRBPlusPlus => vec![Stat::RedPower, Stat::BluePower], - - Spec::Speed => vec![Stat::Speed], - Spec::SpeedRR => vec![Stat::Speed], - Spec::SpeedBB => vec![Stat::Speed], - Spec::SpeedGG => vec![Stat::Speed], - Spec::SpeedRG => vec![Stat::Speed], - Spec::SpeedGB => vec![Stat::Speed], - Spec::SpeedRB => vec![Stat::Speed], - Spec::SpeedRRPlus => vec![Stat::Speed], - Spec::SpeedBBPlus => vec![Stat::Speed], - Spec::SpeedGGPlus => vec![Stat::Speed], - Spec::SpeedRGPlus => vec![Stat::Speed], - Spec::SpeedGBPlus => vec![Stat::Speed], - Spec::SpeedRBPlus => vec![Stat::Speed], - Spec::SpeedRRPlusPlus => vec![Stat::Speed], - Spec::SpeedBBPlusPlus => vec![Stat::Speed], - Spec::SpeedGGPlusPlus => vec![Stat::Speed], - Spec::SpeedRGPlusPlus => vec![Stat::Speed], - Spec::SpeedGBPlusPlus => vec![Stat::Speed], - Spec::SpeedRBPlusPlus => vec![Stat::Speed], - - Spec::Life => vec![Stat::GreenLife], - Spec::LifeRR => vec![Stat::RedLife], - Spec::LifeBB => vec![Stat::BlueLife], - Spec::LifeGG => vec![Stat::GreenLife], - Spec::LifeRG => vec![Stat::GreenLife, Stat::RedLife], - Spec::LifeGB => vec![Stat::GreenLife, Stat::BlueLife], - Spec::LifeRB => vec![Stat::BlueLife, Stat::RedLife], - Spec::LifeRRPlus => vec![Stat::RedLife], - Spec::LifeBBPlus => vec![Stat::BlueLife], - Spec::LifeGGPlus => vec![Stat::GreenLife], - Spec::LifeRGPlus => vec![Stat::GreenLife, Stat::RedLife], - Spec::LifeGBPlus => vec![Stat::GreenLife, Stat::BlueLife], - Spec::LifeRBPlus => vec![Stat::BlueLife, Stat::RedLife], - Spec::LifeRRPlusPlus => vec![Stat::RedLife], - Spec::LifeBBPlusPlus => vec![Stat::BlueLife], - Spec::LifeGGPlusPlus => vec![Stat::GreenLife], - Spec::LifeRGPlusPlus => vec![Stat::GreenLife, Stat::RedLife], - Spec::LifeGBPlusPlus => vec![Stat::GreenLife, Stat::BlueLife], - Spec::LifeRBPlusPlus => vec![Stat::BlueLife, Stat::RedLife], - } - } - - pub fn values(&self) -> SpecValues { - match *self { - Spec::Power => SpecValues { - base: 10, - bonuses: vec![] - }, - - Spec::PowerRR=> SpecValues { - base: 10, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 5 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 9 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 13 } - ], - }, - - Spec::PowerGG=> SpecValues { - base: 10, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 5 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 9 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 13 } - ], - }, - - Spec::PowerBB=> SpecValues { - base: 10, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 5 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 9 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 13 } - ], - }, - - Spec::PowerRG=> SpecValues { - base: 10, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 3 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 6 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 9 } - ], - }, - - Spec::PowerGB=> SpecValues { - base: 10, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 3 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 6 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 9 } - ], - }, - - Spec::PowerRB=> SpecValues { - base: 10, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 3 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 6 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 9 } - ], - }, - - Spec::PowerRRPlus => SpecValues { - base: 15, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 6 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 12 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 18 } - ], - }, - - Spec::PowerGGPlus => SpecValues { - base: 15, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 6 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 12 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 18 } - ], - }, - - Spec::PowerBBPlus => SpecValues { - base: 15, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 6 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 12 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 18 } - ], - }, - - Spec::PowerRGPlus => SpecValues { - base: 15, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 4 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 8 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 12 } - ], - }, - - Spec::PowerGBPlus => SpecValues { - base: 15, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 4 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 8 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 12 } - ], - }, - - Spec::PowerRBPlus => SpecValues { - base: 15, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 4 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 8 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 12 } - ], - }, - Spec::PowerRRPlusPlus => SpecValues { - base: 25, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 8 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 16 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 24 } - ], - }, - - Spec::PowerGGPlusPlus => SpecValues { - base: 25, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 8 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 16 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 24 } - ], - }, - - Spec::PowerBBPlusPlus => SpecValues { - base: 25, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 8 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 16 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 24 } - ], - }, - - Spec::PowerRGPlusPlus => SpecValues { - base: 25, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 5 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 10 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 15 } - ], - }, - - Spec::PowerGBPlusPlus => SpecValues { - base: 25, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 5 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 10 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 15 } - ], - }, - - Spec::PowerRBPlusPlus => SpecValues { - base: 25, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 5 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 10 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 15 } - ], - }, - - Spec::Speed => SpecValues { - base: 40, - bonuses: vec![] - }, - - Spec::SpeedRR=> SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 80 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 80 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 80 } - ], - }, - - Spec::SpeedGG=> SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 80 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 80 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 80 } - ], - }, - - Spec::SpeedBB=> SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 80 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 80 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 80 } - ], - }, - - Spec::SpeedRG=> SpecValues { - base: 60, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 60 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 60 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 60 } - ], - }, - - Spec::SpeedGB=> SpecValues { - base: 60, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 60 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 60 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 60 } - ], - }, - - Spec::SpeedRB=> SpecValues { - base: 60, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 60 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 60 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 60 } - ], - }, - - Spec::SpeedRRPlus => SpecValues { - base: 120, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 120 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 120 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 120 } - ], - }, - - Spec::SpeedGGPlus => SpecValues { - base: 120, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 120 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 120 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 120 } - ], - }, - - Spec::SpeedBBPlus => SpecValues { - base: 120, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 120 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 120 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 120 } - ], - }, - - Spec::SpeedRGPlus => SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 80 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 80 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 80 } - ], - }, - - Spec::SpeedGBPlus => SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 80 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 80 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 80 } - ], - }, - - Spec::SpeedRBPlus => SpecValues { - base: 80, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 80 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 80 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 80 } - ], - }, - - Spec::SpeedRRPlusPlus => SpecValues { - base: 160, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 160 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 160 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 160 } - ], - }, - - Spec::SpeedGGPlusPlus => SpecValues { - base: 160, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 160 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 160 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 160 } - ], - }, - - Spec::SpeedBBPlusPlus => SpecValues { - base: 160, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 160 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 160 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 160 } - ], - }, - - Spec::SpeedRGPlusPlus => SpecValues { - base: 120, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 120 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 120 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 120 } - ], - }, - - Spec::SpeedGBPlusPlus => SpecValues { - base: 120, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 120 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 120 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 120 } - ], - }, - - Spec::SpeedRBPlusPlus => SpecValues { - base: 120, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 120 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 120 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 120 } - ], - }, - - Spec::Life => SpecValues { - base: 125, - bonuses: vec![]}, - - Spec::LifeRR=> SpecValues { - base: 275, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 75 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 125 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 175 } - ], - }, - - Spec::LifeGG=> SpecValues { - base: 225, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 50 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 75 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 125 } - ], - }, - - Spec::LifeBB=> SpecValues { - base: 275, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 75 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 125 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 175 } - ], - }, - - Spec::LifeRG=> SpecValues { - base: 125, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 50 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 75 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 125 } - ], - }, - - Spec::LifeGB=> SpecValues { - base: 125, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 50 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 75 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 125 } - ], - }, - - Spec::LifeRB=> SpecValues { - base: 175, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 50 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 75 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 125 } - ], - }, - - Spec::LifeRRPlus => SpecValues { - base: 500, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 125 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 225 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 300 } - ], - }, - - Spec::LifeGGPlus => SpecValues { - base: 400, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 90 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 130 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 225 } - ], - }, - - Spec::LifeBBPlus => SpecValues { - base: 500, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 125 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 225 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 300 } - ], - }, - - Spec::LifeRGPlus => SpecValues { - base: 225, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 100 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 150 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 225 } - ], - }, - - Spec::LifeGBPlus => SpecValues { - base: 225, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 100 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 150 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 225 } - ], - }, - - Spec::LifeRBPlus => SpecValues { - base: 350, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 100 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 150 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 225 } - ], - }, - Spec::LifeRRPlusPlus => SpecValues { - base: 875, - bonuses: vec![ - SpecBonus { req: Colours { red: 5, green: 0, blue: 0 }, bonus: 225 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 0 }, bonus: 400 }, - SpecBonus { req: Colours { red: 20, green: 0, blue: 0 }, bonus: 525 } - ], - }, - - Spec::LifeGGPlusPlus => SpecValues { - base: 475, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 5, blue: 0 }, bonus: 130 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 0 }, bonus: 225 }, - SpecBonus { req: Colours { red: 0, green: 20, blue: 0 }, bonus: 300 } - ], - }, - - Spec::LifeBBPlusPlus => SpecValues { - base: 875, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 0, blue: 5 }, bonus: 225 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 10 }, bonus: 400 }, - SpecBonus { req: Colours { red: 0, green: 0, blue: 20 }, bonus: 525 } - ], - }, - - Spec::LifeRGPlusPlus => SpecValues { - base: 400, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 2, blue: 0 }, bonus: 175 }, - SpecBonus { req: Colours { red: 5, green: 5, blue: 0 }, bonus: 275 }, - SpecBonus { req: Colours { red: 10, green: 10, blue: 0 }, bonus: 400 } - ], - }, - - Spec::LifeGBPlusPlus => SpecValues { - base: 625, - bonuses: vec![ - SpecBonus { req: Colours { red: 0, green: 2, blue: 2 }, bonus: 175 }, - SpecBonus { req: Colours { red: 0, green: 5, blue: 5 }, bonus: 275 }, - SpecBonus { req: Colours { red: 0, green: 10, blue: 10 }, bonus: 400 } - ], - }, - - Spec::LifeRBPlusPlus => SpecValues { - base: 400, - bonuses: vec![ - SpecBonus { req: Colours { red: 2, green: 0, blue: 2 }, bonus: 175 }, - SpecBonus { req: Colours { red: 5, green: 0, blue: 5 }, bonus: 275 }, - SpecBonus { req: Colours { red: 10, green: 0, blue: 10 }, bonus: 400 } - ], - }, - } - } - - pub fn apply(&self, modified: u64, base: u64, player_colours: &Colours) -> u64 { - match *self { - // Percentage multipliers based on base value - Spec::Power | - Spec::Speed => modified + base.pct(self.values().base), - Spec::PowerRR| - Spec::PowerGG| - Spec::PowerBB| - Spec::PowerRG| - Spec::PowerGB| - Spec::PowerRB| - Spec::PowerRRPlus | - Spec::PowerGGPlus | - Spec::PowerBBPlus | - Spec::PowerRGPlus | - Spec::PowerGBPlus | - Spec::PowerRBPlus | - Spec::PowerRRPlusPlus | - Spec::PowerGGPlusPlus | - Spec::PowerBBPlusPlus | - Spec::PowerRGPlusPlus | - Spec::PowerGBPlusPlus | - Spec::PowerRBPlusPlus | - - Spec::SpeedRR| - Spec::SpeedGG| - Spec::SpeedBB| - Spec::SpeedRG| - Spec::SpeedGB| - Spec::SpeedRB| - Spec::SpeedRRPlus | - Spec::SpeedGGPlus | - Spec::SpeedBBPlus | - Spec::SpeedRGPlus | - Spec::SpeedGBPlus | - Spec::SpeedRBPlus | - Spec::SpeedRRPlusPlus | - Spec::SpeedGGPlusPlus | - Spec::SpeedBBPlusPlus | - Spec::SpeedRGPlusPlus | - Spec::SpeedGBPlusPlus | - Spec::SpeedRBPlusPlus => modified + base.pct(self.values().max_value(player_colours)), - - // Flat bonus - Spec::Life => modified + self.values().base, - Spec::LifeRR| - Spec::LifeGG| - Spec::LifeBB| - Spec::LifeRG| - Spec::LifeGB| - Spec::LifeRB| - Spec::LifeRRPlus | - Spec::LifeGGPlus | - Spec::LifeBBPlus | - Spec::LifeRGPlus | - Spec::LifeGBPlus | - Spec::LifeRBPlus | - Spec::LifeRRPlusPlus | - Spec::LifeGGPlusPlus | - Spec::LifeBBPlusPlus | - Spec::LifeRGPlusPlus | - Spec::LifeGBPlusPlus | - Spec::LifeRBPlusPlus => modified + self.values().max_value(player_colours), - } - } -} diff --git a/server/src/util.rs b/server/src/util.rs deleted file mode 100644 index bf4f3032..00000000 --- a/server/src/util.rs +++ /dev/null @@ -1,40 +0,0 @@ -// use net::Db; -// Db Commons -// use failure::Error; - -// pub fn startup(db: Db) -> Result<(), Error> { -// let tx = db.transaction()?; - -// info!("running startup fns"); - -// match tx.commit() { -// Ok(_) => { -// info!("startup processes completed"); -// Ok(()) -// }, -// Err(e) => Err(format_err!("failed to commit startup tx {:?}", e)), -// } -// } - -pub trait IntPct { - fn pct(self, pct: u64) -> u64; -} - -impl IntPct for u64 { - fn pct(self, pct: u64) -> u64 { - self * pct / 100 - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn int_pct_test() { - assert_eq!(100.pct(110), 110); - assert_eq!(100.pct(50), 50); - assert_eq!(1.pct(200), 2); - assert_eq!(1.pct(50), 0); - } -} \ No newline at end of file diff --git a/server/src/vbox.rs b/server/src/vbox.rs deleted file mode 100644 index fdfd1d3d..00000000 --- a/server/src/vbox.rs +++ /dev/null @@ -1,345 +0,0 @@ -use uuid::Uuid; - -use std::iter; -use std::collections::HashMap; - -// refunds -use rand::prelude::*; -use rand::{thread_rng}; -use rand::distributions::{WeightedIndex}; - -use postgres::transaction::Transaction; - -use failure::Error; -use failure::err_msg; - -use account::Account; - -use instance::{Instance, instance_get, instance_update}; -use construct::{Colours}; - -use item::*; - -pub type VboxIndices = Option>>; - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct Vbox { - pub bits: usize, - pub store: HashMap>, - pub stash: HashMap, -} - -#[derive(Debug,Copy,Clone,Serialize,Deserialize,Hash,PartialEq,Eq)] -pub enum ItemType { - Colours, - Skills, - Specs, -} - -const STORE_COLOURS_CAPACITY: usize = 6; -const STORE_SKILLS_CAPACITY: usize = 3; -const STORE_SPECS_CAPACITY: usize = 3; -const STASH_CAPACITY: usize = 6; -const STARTING_ATTACK_COUNT: usize = 3; - -impl Vbox { - pub fn new() -> Vbox { - let mut colours: HashMap = HashMap::new(); - let mut skills: HashMap = HashMap::new(); - let mut specs: HashMap = HashMap::new(); - - let store = [ - (ItemType::Colours, colours), - (ItemType::Skills, skills), - (ItemType::Colours, specs), - ].iter().cloned().collect(); - - let mut stash = HashMap::new(); - for i in 0..STARTING_ATTACK_COUNT { - stash.insert(i.to_string(), Item::Attack); - } - - Vbox { - store, - stash, - bits: 30, - } - } - - pub fn balance_sub(&mut self, amount: usize) -> Result<&mut Vbox, Error> { - let new_balance = self.bits - .checked_sub(amount) - .ok_or(format_err!("insufficient balance: {:?}", self.bits))?; - - self.bits = new_balance; - - Ok(self) - } - - pub fn balance_add(&mut self, amount: usize) -> &mut Vbox { - self.bits = self.bits.saturating_add(amount); - self - } - - pub fn fill(&mut self) -> &mut Vbox { - let mut rng = thread_rng(); - - let colours = vec![ - (Item::Red, 1), - (Item::Green, 1), - (Item::Blue, 1), - ]; - let colour_dist = WeightedIndex::new(colours.iter().map(|item| item.1)).unwrap(); - - let skills = vec![ - (Item::Attack, 1), - (Item::Block, 1), - (Item::Buff, 1), - (Item::Debuff, 1), - (Item::Stun, 1), - ]; - let skill_dist = WeightedIndex::new(skills.iter().map(|item| item.1)).unwrap(); - - let specs = vec![ - (Item::Power, 1), - (Item::Life, 1), - (Item::Speed, 1), - ]; - let spec_dist = WeightedIndex::new(specs.iter().map(|item| item.1)).unwrap(); - - for item_type in [ItemType::Colours, ItemType::Skills, ItemType::Specs].iter() { - let (items, num, dist) = match item_type { - ItemType::Colours => (&colours, STORE_COLOURS_CAPACITY, &colour_dist), - ItemType::Skills => (&skills, STORE_SKILLS_CAPACITY, &skill_dist), - ItemType::Specs => (&specs, STORE_SPECS_CAPACITY, &spec_dist), - }; - - let drops = iter::repeat_with(|| items[dist.sample(&mut rng)].0) - .take(num) - .enumerate() - .map(|(i, item)| (i.to_string(), item)) - .collect::>(); - - self.store.insert(*item_type, drops); - } - - self - } - - pub fn buy(&mut self, item: ItemType, i: &String) -> Result { - // check item exists - let selection = self.store - .get_mut(&item).ok_or(format_err!("no item group {:?}", item))? - .remove(i).ok_or(format_err!("no item at index {:?} {:}", self, i))?; - - self.balance_sub(selection.cost())?; - - Ok(selection) - } - - pub fn stash_add(&mut self, item: Item, index: Option<&String>) -> Result { - if self.stash.len() >= STASH_CAPACITY { - return Err(err_msg("stash full")); - } - - if let Some(index) = index { - if self.stash.contains_key(index) { - return Err(format_err!("slot occupied {:?}", index)); - } - self.stash.insert(index.clone(), item); - return Ok(index.to_string()); - } - - for i in (0..STASH_CAPACITY).map(|i| i.to_string()) { - if !self.stash.contains_key(&i) { - self.stash.insert(i.clone(), item); - return Ok(i); - } - } - - return Err(err_msg("stash full")); - } - - pub fn bot_buy(&mut self, item: ItemType) -> Result { - let buy_index = self.store[&item] - .keys() - .next() - .ok_or(format_err!("no item in group {:?}", item))? - .clone(); - - self.buy(item, &buy_index) - } - - pub fn refund(&mut self, i: String) -> Result<&mut Vbox, Error> { - let refunded = self.stash.remove(&i) - .ok_or(format_err!("no item at index {:?} {:?}", self.stash, i))?; - - let refund = refunded.cost(); - // info!("refunding {:?} for {:?}", refund, refunded); - self.balance_add(refund); - Ok(self) - } - - pub fn combine(&mut self, stash_indices: Vec, store_indices: Option>>) -> Result<&mut Vbox, Error> { - // find base item for index to insert into - let base_index = stash_indices.iter() - .find(|i| match self.stash.get(i.clone()) { - Some(item) => item.into_skill().is_some(), - None => false, - }); - - let mut input = stash_indices - .iter() - .map(|i| self.stash.remove(i) - .ok_or(format_err!("no item at index {:?} {:?}", self.stash, i))) - .collect::, Error>>()?; - - if let Some(store_indices) = store_indices { - let mut purchased = store_indices.iter() - .map(|(g, list)| - list.iter() - .map(|i| self.buy(*g, i)) - .collect::, Error>>() - ) - .collect::>, Error>>()? - .into_iter() - .flatten() - .collect(); - - input.append(&mut purchased); - } - - // sort the input to align with the combinations - // combos are sorted when created - input.sort_unstable(); - let combos = get_combos(); - let combo = combos.iter().find(|c| c.components == input).ok_or(err_msg("not a combo"))?; - - self.stash_add(combo.item, base_index)?; - - Ok(self) - } -} - -pub fn vbox_refill(tx: &mut Transaction, account: &Account, instance_id: Uuid) -> Result { - let instance = instance_get(tx, instance_id)? - .vbox_refill(account.id)?; - return instance_update(tx, instance); -} - -pub fn vbox_buy(tx: &mut Transaction, account: &Account, instance_id: Uuid, group: ItemType, index: String, construct_id: Option) -> Result { - let instance = instance_get(tx, instance_id)? - .vbox_buy(account.id, group, index, construct_id)?; - return instance_update(tx, instance); -} - -pub fn vbox_combine(tx: &mut Transaction, account: &Account, instance_id: Uuid, stash_indices: Vec, vbox_indices: VboxIndices) -> Result { - let instance = instance_get(tx, instance_id)? - .vbox_combine(account.id, stash_indices, vbox_indices)?; - return instance_update(tx, instance); -} - -pub fn vbox_refund(tx: &mut Transaction, account: &Account, instance_id: Uuid, index: String) -> Result { - let instance = instance_get(tx, instance_id)? - .vbox_refund(account.id, index)?; - return instance_update(tx, instance); -} - -pub fn vbox_apply(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, index: String) -> Result { - let instance = instance_get(tx, instance_id)? - .vbox_apply(account.id, index, construct_id)?; - return instance_update(tx, instance); -} - -pub fn vbox_unequip(tx: &mut Transaction, account: &Account, instance_id: Uuid, construct_id: Uuid, target: Item, target_construct_id: Option) -> Result { - let instance = instance_get(tx, instance_id)? - .vbox_unequip(account.id, target, construct_id, target_construct_id)?; - return instance_update(tx, instance); -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn combine_test() { - let mut vbox = Vbox::new(); - vbox.stash.insert(0.to_string(), Item::Attack); - vbox.stash.insert(1.to_string(), Item::Green); - vbox.stash.insert(2.to_string(), Item::Green); - vbox.combine(vec![0.to_string(), 1.to_string(), 2.to_string()], None).unwrap(); - assert_eq!(vbox.stash["0"], Item::Heal); - } - - #[test] - fn buy_test() { - let mut vbox = Vbox::new(); - vbox.fill(); - - // cannot rebuy same - vbox.buy(ItemType::Skills, &0.to_string()).unwrap(); - assert!(vbox.store[&ItemType::Skills].get(&0.to_string()).is_none()); - assert!(vbox.buy(ItemType::Skills, &0.to_string()).is_err()); - } - - #[test] - fn capacity_test() { - let mut vbox = Vbox::new(); - vbox.fill(); - vbox.stash_add(Item::Red, None).unwrap(); - vbox.stash_add(Item::Red, None).unwrap(); - vbox.stash_add(Item::Red, None).unwrap(); - assert!(vbox.stash_add(Item::Red, None).is_err()); - } - - #[test] - fn store_and_stash_combine_test() { - let mut vbox = Vbox::new(); - vbox.fill(); - - let mut skill_combine_args = HashMap::new(); - skill_combine_args.insert(ItemType::Colours, vec![0.to_string(), 1.to_string()]); - skill_combine_args.insert(ItemType::Skills, vec![0.to_string()]); - - let mut spec_combine_args = HashMap::new(); - spec_combine_args.insert(ItemType::Colours, vec![2.to_string(), 3.to_string()]); - spec_combine_args.insert(ItemType::Specs, vec![0.to_string()]); - - vbox.combine(vec![], Some(skill_combine_args)).unwrap(); - vbox.combine(vec![], Some(spec_combine_args)).unwrap(); - } - - #[test] - fn combos_test() { - let mut input = vec![Item::Green, Item::Attack, Item::Green]; - let combos = get_combos(); - - // sort input so they align - input.sort_unstable(); - - let combo = combos.iter().find(|c| c.components == input); - assert!(combo.is_some()); - } - - #[test] - fn refund_test() { - let mut vbox = Vbox::new(); - vbox.stash.insert(0.to_string(), Item::Strike); - vbox.refund(0.to_string()).unwrap(); - assert_eq!(vbox.bits, 32); - } - - #[test] - fn colours_count_test() { - let strike = Item::Strike; - - let mut count = Colours::new(); - strike.colours(&mut count); - assert_eq!(count.red, 2); - } - - // #[test] - // fn item_info_test() { - // info!("{:#?}", item_info()); - // } -} \ No newline at end of file diff --git a/server/src/warden.rs b/server/src/warden.rs index ff4b2760..1e6a729a 100644 --- a/server/src/warden.rs +++ b/server/src/warden.rs @@ -9,12 +9,20 @@ use postgres::transaction::Transaction; use failure::Error; use account; -use game::{games_need_upkeep, game_update, game_write, game_delete}; -use instance; -use instance::{instances_need_upkeep, instances_idle, instance_update, instance_delete}; -use pg::{Db, PgPool}; use events::{Event, EventsTx, PvpRequest}; use rpc::{RpcMessage}; +use pg::{ + PgPool, + + games_need_upkeep, + game_update, + game_write, + game_delete, + instances_need_upkeep, + instances_idle, + instance_update, + pvp, +}; type Id = usize; type Pair = (PvpRequest, PvpRequest); @@ -100,7 +108,7 @@ impl Warden { let a = account::select(&db, pair.0.account)?; let b = account::select(&db, pair.1.account)?; - let instance = instance::pvp(&mut tx, &a, &b)?; + let instance = pvp(&mut tx, &a, &b)?; tx.commit()?; // subscribe users to instance events diff --git a/server/src/zone.rs b/server/src/zone.rs deleted file mode 100644 index f824587f..00000000 --- a/server/src/zone.rs +++ /dev/null @@ -1,417 +0,0 @@ -use uuid::Uuid; -use petgraph::graph::{Graph, UnGraph, NodeIndex}; -// use petgraph::dot::{Dot, Config}; - -// Db Commons -use account::Account; -use serde_cbor::{from_slice, to_vec}; -use postgres::transaction::Transaction; -use failure::Error; -use failure::err_msg; - -// shapes -use rand::prelude::*; -use rand::{thread_rng}; -use rand::distributions::{WeightedIndex}; - -use game::{Game, GameMode, game_pve_new, game_write}; -use rpc::{ZoneJoinParams, ZoneCloseParams}; - -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct Zone { - id: Uuid, - account: Uuid, - active: bool, - graph: UnGraph, -} - -#[derive(Debug,Clone,Copy)] -enum Shape { - Diamond, - Line, - Plus, - Diode, - Domino, - Kite, -} - -pub type ZoneGraph = UnGraph; - -#[derive(Debug,Clone,PartialEq,Eq,Hash,PartialOrd,Ord,Serialize,Deserialize)] -pub struct Encounter { - tag: String, - game_id: Option, - success: bool, - x: i8, - y: i8, -} - -impl Encounter { - fn new(tag: &'static str, x: i8, y: i8) -> Encounter { - return Encounter { - tag: tag.to_string(), - success: false, - game_id: None, - x, - y, - }; - } - fn start() -> Encounter { - return Encounter { - tag: "START".to_string(), - success: true, - game_id: None, - x: 0, - y: 0, - }; - } -} - -pub fn zone_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { - let query = " - DELETE - FROM zones - WHERE id = $1; - "; - - let result = tx - .execute(query, &[&id])?; - - if result != 1 { - return Err(format_err!("unable to delete zone {:?}", id)); - } - - info!("zone deleted {:?}", id); - - return Ok(()); -} - -pub fn zone_get(tx: &mut Transaction, id: Uuid) -> Result { - let query = " - SELECT * - FROM zones - WHERE id = $1 - "; - - let result = tx - .query(query, &[&id])?; - - let returned = match result.iter().next() { - Some(row) => row, - None => return Err(err_msg("zone not found")), - }; - - // tells from_slice to cast into a construct - let bytes: Vec = returned.get("data"); - let zone = match from_slice::(&bytes) { - Ok(z) => z, - Err(_) => { - zone_delete(tx, id)?; - return Err(err_msg("invalid zone removed")) - }, - }; - - return Ok(zone); -} - -pub fn zone_create(tx: &mut Transaction, account: &Account) -> Result { - let id = Uuid::new_v4(); - let graph = create_zone_graph(); - - let zone = Zone { - id, - account: account.id, - graph, - active: true, - }; - - let bytes = to_vec(&zone)?; - - let query = " - INSERT INTO zones (id, data, account) - VALUES ($1, $2, $3) - RETURNING id; - "; - - let result = tx - .query(query, &[&id, &bytes, &account.id])?; - - result.iter().next().ok_or(format_err!("no zone written"))?; - - return Ok(zone); -} - -pub fn zone_update(zone: &Zone, tx: &mut Transaction) -> Result<(), Error> { - let bytes = to_vec(&zone)?; - - let query = " - UPDATE zones - SET data = $1, active = $2 - WHERE id = $3 - RETURNING id, data; - "; - - let result = tx - .query(query, &[&bytes, &zone.active, &zone.id])?; - - result.iter().next().ok_or(format_err!("zone {:?} could not be written", zone))?; - - return Ok(()); -} - -pub fn zone_join(params: ZoneJoinParams, tx: &mut Transaction, account: &Account) -> Result { - let mut zone = zone_get(tx, params.zone_id)?; - let mut game; - - // check node joinable - node_joinable(&zone.graph, NodeIndex::from(params.node_id))?; - - // borrow zone.graph to make the game - { - let node_index = NodeIndex::from(params.node_id); - let encounter = zone.graph - .node_weight_mut(node_index) - .ok_or(err_msg("invalid encounter id"))?; - - let mode = match encounter.tag.as_ref() { - "NORMAL" => GameMode::Zone3v2Attack, - "CASTER" => GameMode::Zone2v2Caster, - "MINIBOSS" => GameMode::Zone3v3MeleeMiniboss, - "BOSS" => GameMode::Zone3v3HealerBoss, - _ => return Err(err_msg("unknown zone tag")), - }; - game = game_pve_new(params.construct_ids, mode, tx, account)?; - game.set_zone(zone.id, params.node_id); - - encounter.game_id = Some(game.id); - } - - // persist - game_write(tx, &game)?; - zone_update(&zone, tx)?; - - return Ok(game); -} - -pub fn zone_close(params: ZoneCloseParams, tx: &mut Transaction, _account: &Account) -> Result<(), Error> { - let mut zone = zone_get(tx, params.zone_id)?; - zone.active = false; - zone_update(&zone, tx)?; - return Ok(()); -} - -// shapes should always add the exit normal node -fn add_shape(shape: Shape, gr: &mut ZoneGraph, start: NodeIndex, x: i8, y: i8) -> (NodeIndex, i8, i8) { - match shape { - Shape::Line => { - let mut next = gr.add_node(Encounter::new("CASTER", x, y)); - gr.add_edge(start, next, ()); - - let exit = gr.add_node(Encounter::new("NORMAL", x + 1, y)); - gr.add_edge(next, exit, ()); - - return (exit, x + 1, y); - }, - Shape::Plus => { - let top = gr.add_node(Encounter::new("MINIBOSS", x, y + 1)); - gr.add_edge(start, top, ()); - - let bottom = gr.add_node(Encounter::new("MINIBOSS", x, y - 1)); - gr.add_edge(start, bottom, ()); - - let exit = gr.add_node(Encounter::new("NORMAL", x + 1, y)); - gr.add_edge(start, exit, ()); - - return (exit, x + 1, y); - }, - Shape::Diode => { - let top = gr.add_node(Encounter::new("MINIBOSS", x, y + 1)); - gr.add_edge(start, top, ()); - - let bottom = gr.add_node(Encounter::new("MINIBOSS", x, y - 1)); - gr.add_edge(start, bottom, ()); - - let exit = gr.add_node(Encounter::new("NORMAL", x + 1, y)); - gr.add_edge(start, exit, ()); - - // connect top and exit for an extra chance - gr.add_edge(top, exit, ()); - gr.add_edge(bottom, exit, ()); - - return (exit, x + 1, y); - }, - Shape::Kite => { - let top = gr.add_node(Encounter::new("MINIBOSS", x + 1, y + 1)); - gr.add_edge(start, top, ()); - - let top_tip = gr.add_node(Encounter::new("BOSS", x + 1, y + 2)); - gr.add_edge(top, top_tip, ()); - - let bottom = gr.add_node(Encounter::new("MINIBOSS", x + 1, y - 1)); - gr.add_edge(start, bottom, ()); - - let bottom_tip = gr.add_node(Encounter::new("BOSS", x + 1, y - 2)); - gr.add_edge(bottom, bottom_tip, ()); - - let side = gr.add_node(Encounter::new("CASTER", x + 2, y)); - gr.add_edge(start, side, ()); - gr.add_edge(top, side, ()); - gr.add_edge(bottom, side, ()); - - let exit = gr.add_node(Encounter::new("NORMAL", x + 3, y)); - gr.add_edge(side, exit, ()); - - return (exit, x + 3, y); - }, - Shape::Domino => { - let top = gr.add_node(Encounter::new("NORMAL", x, y + 1)); - gr.add_edge(start, top, ()); - - let top_tip = gr.add_node(Encounter::new("MINIBOSS", x + 1, y + 1)); - gr.add_edge(top, top_tip, ()); - - let bottom = gr.add_node(Encounter::new("MINIBOSS", x, y - 1)); - gr.add_edge(start, bottom, ()); - - let bottom_tip = gr.add_node(Encounter::new("NORMAL", x + 1, y - 1)); - gr.add_edge(bottom, bottom_tip, ()); - - let side = gr.add_node(Encounter::new("CASTER", x + 1, y)); - gr.add_edge(top_tip, side, ()); - gr.add_edge(bottom_tip, side, ()); - - let exit = gr.add_node(Encounter::new("NORMAL", x + 2, y)); - gr.add_edge(side, exit, ()); - - return (exit, x + 2, y); - }, - Shape::Diamond => { - let top = gr.add_node(Encounter::new("NORMAL", x + 1, y + 1)); - gr.add_edge(start, top, ()); - - let top_tip = gr.add_node(Encounter::new("MINIBOSS", x + 2, y + 1)); - gr.add_edge(top, top_tip, ()); - - let bottom = gr.add_node(Encounter::new("MINIBOSS", x + 1, y - 1)); - gr.add_edge(start, bottom, ()); - - let bottom_tip = gr.add_node(Encounter::new("NORMAL", x + 2, y - 1)); - gr.add_edge(bottom, bottom_tip, ()); - - let exit = gr.add_node(Encounter::new("CASTER", x + 3, y)); - gr.add_edge(top_tip, exit, ()); - gr.add_edge(bottom_tip, exit, ()); - - return (exit, x + 3, y); - }, - // _ => panic!("nyi shape"), - } -} - -pub fn create_zone_graph() -> ZoneGraph { - let mut gr = Graph::new_undirected(); - let mut rng = thread_rng(); - - let mut last = gr.add_node(Encounter::start()); - let mut x = 0; - let mut y = 0; - - for _i in 0..4 { - let shapes = vec![ - (Shape::Line, 1), - (Shape::Diamond, 1), - (Shape::Diode, 1), - (Shape::Kite, 1), - (Shape::Domino, 1), - (Shape::Plus, 1), - ]; - - let dist = WeightedIndex::new(shapes.iter().map(|item| item.1)).unwrap(); - let shape = shapes[dist.sample(&mut rng)].0; - - let result = add_shape(shape, &mut gr, last, x, y); - last = result.0; - x = result.1; - y = result.2; - } - - return gr; -} - -pub fn node_joinable(graph: &ZoneGraph, target_index: NodeIndex) -> Result<(), Error> { - // early return for already attempted - { - let target_encounter = match graph.node_weight(target_index) { - Some(encounter) => encounter, - None => panic!("{:?} has no weight for {:?}", graph, target_index), - }; - - if target_encounter.game_id.is_some() { - return Err(err_msg("node already attempted")); - } - } - - let success_indices = graph.node_indices().filter(|i| { - match graph.node_weight(*i) { - Some(encounter) => encounter.success, - None => panic!("no weight for {:?}", i), - } - }); - - // if a node is a neighbour of that graph - // and hasn't been attempted - // it is joinable - for i in success_indices { - match graph.neighbors(i).find(|n| *n == target_index) { - Some(_n) => return Ok(()), - None => continue, - }; - } - - return Err(err_msg("node requirements not met")); -} - -pub fn node_finish(game: &Game, zone_id: Uuid, node_index: u32, tx: &mut Transaction) -> Result { - let mut zone = zone_get(tx, zone_id)?; - - let winner_id = match game.winner() { - Some(w) => w.id, - None => return Ok(zone), - }; - - if zone.account == winner_id { - { - let encounter = zone.graph - .node_weight_mut(NodeIndex::from(node_index)) - .ok_or(err_msg("encounter not found for game zone update"))?; - - encounter.success = true; - } - zone_update(&zone, tx)?; - } - - Ok(zone) -} - -#[cfg(test)] -mod tests { - use zone::*; - - #[test] - fn create_zone_test() { - let _graph = create_zone_graph(); - // good shit; - // let nodes = graph.node_indices().collect::>(); - // info!("{:?}", nodes[0]); - // info!("{:?}", graph.node_weight(nodes[0])); - - // info!("{:?}", Dot::with_config(&graph, &[Config::EdgeNoLabel])); - } - - #[test] - fn zone_joinable_test() { - let graph = create_zone_graph(); - assert!(node_joinable(&graph, NodeIndex::from(1)).is_ok()); - assert!(node_joinable(&graph, NodeIndex::from(graph.node_count() as u32 - 1)).is_err()); - } -}