diff --git a/client/src/components/game.component.jsx b/client/src/components/game.component.jsx index 338f6121..fbe614d1 100644 --- a/client/src/components/game.component.jsx +++ b/client/src/components/game.component.jsx @@ -19,6 +19,7 @@ function GamePanel(props) { setActiveCryp, selectSkillTarget, sendInstanceState, + sendGameReady, activeCryp, account, showLog, @@ -69,18 +70,23 @@ function GamePanel(props) { onClick={() => toggleLog(!showLog)}> Log + ); function findCryp(id) { - const team = game.teams.find(t => t.cryps.find(c => c.id === id)); + const team = game.players.find(t => t.cryps.find(c => c.id === id)); if (team) return team.cryps.find(c => c.id === id); return null; } - const otherTeams = game.teams.filter(t => t.id !== account.id); + const otherTeams = game.players.filter(t => t.id !== account.id); - const playerTeam = game.teams.find(t => t.id === account.id); + const playerTeam = game.players.find(t => t.id === account.id); function stackElement(c, i) { let skills = game.stack.filter(s => s.source_cryp_id === c.id).map((s, j) => { diff --git a/client/src/components/game.container.jsx b/client/src/components/game.container.jsx index e2be7224..9241ea09 100644 --- a/client/src/components/game.container.jsx +++ b/client/src/components/game.container.jsx @@ -23,6 +23,10 @@ const addState = connect( return false; } + function sendGameReady() { + ws.sendGameReady(game.id); + } + function sendInstanceState(instanceId) { if (!instanceId) return false; return ws.sendInstanceState(instanceId); @@ -42,6 +46,7 @@ const addState = connect( activeCryp, selectSkillTarget, sendInstanceState, + sendGameReady, }; }, diff --git a/client/src/components/info.component.jsx b/client/src/components/info.component.jsx index 9d395a9c..6919ed49 100644 --- a/client/src/components/info.component.jsx +++ b/client/src/components/info.component.jsx @@ -153,15 +153,39 @@ function Info(args) { ); } + function playerRound(id) { + if (!instance.rounds.length) return null; + return instance.rounds[instance.rounds.length - 1].find(r => r.player_ids.includes(id)); + } + + function playerText(player) { + const round = playerRound(player.id); + if (!round) { + return player.ready + ? 'ready' + : ''; + } + + if (round.finished) return 'finished'; + + return player.ready + ? 'ready' + : ''; + } + function scoreBoard() { - const players = instance.players.map((p, i) => - - {p.name} - {p.score.wins} / {p.score.losses} - {p.ready ? 'ready' : ''} - - ); + const players = instance.players.map((p, i) => { + const pText = playerText(p); + console.log(pText); + return ( + + {p.name} + {p.score.wins} / {p.score.losses} + {pText} + + ); + }); return ( diff --git a/client/src/components/instance.create.form.jsx b/client/src/components/instance.create.form.jsx index f8b5f8cb..4e52fbc9 100644 --- a/client/src/components/instance.create.form.jsx +++ b/client/src/components/instance.create.form.jsx @@ -67,10 +67,11 @@ class InstanceCreateForm extends Component {
- {mmSet} ); } diff --git a/client/src/components/vbox.component.jsx b/client/src/components/vbox.component.jsx index ba0de3f4..b354ca4a 100644 --- a/client/src/components/vbox.component.jsx +++ b/client/src/components/vbox.component.jsx @@ -110,7 +110,7 @@ function Vbox(args) { if (boundTimer) { clearTimeout(boundTimer); - if (reclaiming && i) sendVboxReclaim(i); + if (reclaiming && vbox.bound[i]) sendVboxReclaim(i); else if (vbox.bound[i]) { const insert = combiner.findIndex(j => j === null); if (insert === -1) return setCombiner([i, null, null]); @@ -128,7 +128,7 @@ function Vbox(args) { } function boundClick(e, i) { - if (reclaiming && i) sendVboxReclaim(i); + if (reclaiming && vbox.bound[i]) sendVboxReclaim(i); else if (vbox.bound[i]) { const insert = combiner.findIndex(j => j === null); if (insert === -1) return setCombiner([i, null, null]); @@ -210,6 +210,7 @@ function Vbox(args) { return setReclaiming(!reclaiming); } +console.log('reclaiminig', reclaiming) const classes = `vbox ${activeVar !== null || activeCryp ? 'hidden' : ''}`; const reclaimClass = `instance-btn instance-ui-btn vbox-btn ${reclaiming ? 'reclaiming' : ''}`; diff --git a/client/src/keyboard.jsx b/client/src/keyboard.jsx index 14392540..73fadac8 100644 --- a/client/src/keyboard.jsx +++ b/client/src/keyboard.jsx @@ -8,6 +8,7 @@ function setupKeys(store) { key('esc', () => store.dispatch(actions.setReclaiming(false))); key('esc', () => store.dispatch(actions.setActiveSkill(null))); key('esc', () => store.dispatch(actions.setActiveCryp(null))); + key('esc', () => store.dispatch(actions.setInfo([null, null]))); } module.exports = setupKeys; diff --git a/client/src/socket.jsx b/client/src/socket.jsx index 9cbc225c..e7716740 100644 --- a/client/src/socket.jsx +++ b/client/src/socket.jsx @@ -74,8 +74,8 @@ function createSocket(events) { send({ method: 'game_state', params: { id } }); } - function sendGameJoin(gameId, crypIds) { - send({ method: 'game_join', params: { game_id: gameId, cryp_ids: crypIds } }); + function sendGameReady(id) { + send({ method: 'game_ready', params: { id } }); } function sendSpecForget(id, spec) { @@ -331,7 +331,7 @@ function createSocket(events) { sendAccountInstances, sendAccountZone, sendGameState, - sendGameJoin, + sendGameReady, sendGameSkill, sendGameTarget, sendCrypSpawn, diff --git a/ops/migrations/20181020104420_games.js b/ops/migrations/20181020104420_games.js index 5f1b1531..ec04e157 100644 --- a/ops/migrations/20181020104420_games.js +++ b/ops/migrations/20181020104420_games.js @@ -6,6 +6,10 @@ exports.up = async knex => { table.index('id'); table.timestamps(true, true); table.binary('data').notNullable(); + table.boolean('finished') + .defaultTo(false) + .notNullable() + .index(); }); await knex.schema.createTable('instances', async table => { @@ -15,7 +19,7 @@ exports.up = async knex => { table.binary('data').notNullable(); table.boolean('open') - .defaultTo(true) + .defaultTo(false) .notNullable() .index(); }); diff --git a/server/Cargo.toml b/server/Cargo.toml index a59216f9..d2e1c30d 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -10,9 +10,10 @@ serde = "1" serde_derive = "1" serde_cbor = "0.9" +chrono = { version = "0.4", features = ["serde"] } + tungstenite = "0.6" bcrypt = "0.2" -petgraph = { version = "0.4", features = ["serde-1"] } dotenv = "0.9.0" env_logger = "*" diff --git a/server/ECONOMY.md b/server/ECONOMY.md index c2813d92..cd15efb1 100644 --- a/server/ECONOMY.md +++ b/server/ECONOMY.md @@ -2,12 +2,12 @@ Items - Base colours / skills / specs and associated upgrades -### Sources of money +### Sources of money - Start with money and gain income after each battle -- Higher income from winning +- Higher income from winning - Selling items in inventory or equipped on character refunds - - Selling from inventory full refund + - Selling from inventory full refund - Selling from charcter 50% refund ### Uses for money @@ -32,7 +32,7 @@ Base specs have a base 3 cost Round #1 All costs are base costs -# Team #1 and Team #2 (They both bought the same things) +# Player #1 and Player #2 (They both bought the same things) Cryp #1 Strike (Attack + RR), (2 + 1 + 1) = (4) cost Cryp #1 Empower (Buff + RR), (2 + 1 + 1) = (4) cost Cryp #3 Attack, 2 cost @@ -51,7 +51,7 @@ The costs of red for round #2 are now (1 + 1) = 2 If they were to buy the same skill setup it would be as follows: -# Team #1 and Team #2 (They both bought the same things) +# Player #1 and Player #2 (They both bought the same things) Cryp #1 Strike (Attack + RR), (2 + 2 + 2) = (6) cost Cryp #1 Empower (Buff + RR), (2 + 2 + 2) = (6) cost Cryp #3 Attack, 2 cost @@ -60,8 +60,8 @@ Total cost - 14 ### Philosophy of increasing item costs -- Two games will never feel exactly the same -- Costs change over rounds to diversify skill choice and gameplay +- Two games will never feel exactly the same +- Costs change over rounds to diversify skill choice and gameplay - As optimal builds emerge the paths to reach them will change every game - Rewarded for going (hipster) builds nobody else is trying - Some reward for hoarding items in your inventory while they cheaper (hodl red) diff --git a/server/README.md b/server/README.md index 19dbe7c4..74cb4925 100644 --- a/server/README.md +++ b/server/README.md @@ -4,16 +4,16 @@ skill phase: 1.1 -> block (sp 10) -> on self -1.2 -> attack (sp 5) -> on team 2 +1.2 -> attack (sp 5) -> on player 2 -2.1 -> hex (sp 3) -> on team 1 -2.2 -> attack (sp 5) -> on team 1 +2.1 -> hex (sp 3) -> on player 1 +2.2 -> attack (sp 5) -> on player 1 target phase: -team 2 targets 1.2 on 2.2 +player 2 targets 1.2 on 2.2 -team 1 targets 2.1 on 1.1 -team 1 targets 2.2 on 1.1 +player 1 targets 2.1 on 1.1 +player 1 targets 2.2 on 1.1 resolve phase: 1.1 <- block @@ -116,7 +116,7 @@ Its members now scour the lands in search of magic, censoring its teaching, purg * Silence * Magic resistance * Information gathering - * team composition + * player composition * available skills etc Universal Chaos diff --git a/server/SPECS.md b/server/SPECS.md index eaf40e55..543247df 100644 --- a/server/SPECS.md +++ b/server/SPECS.md @@ -1,10 +1,10 @@ ### Specs ### Numbers are placeholder -`Specs get a bonus dependent on the total of Red / Green / Blue in team skills & specs` +`Specs get a bonus dependent on the total of Red / Green / Blue in player skills & specs` # Example to meet 5 red gem bonus from skills only -In your team Cryp #1 has `Strike`, Cryp #2 has `Slay` and `Heal`, Cryp #3 has `Snare` +In your player Cryp #1 has `Strike`, Cryp #2 has `Slay` and `Heal`, Cryp #3 has `Snare` - RR skill `Strike` contributes 2 red gems to the total red gems (2 total) - RG skill `Slay` contributes 1 red gem to the total red gems (3 total) - GG skill `Heal` contirubtes 0 red gems to the total red gems (3 total) @@ -30,7 +30,7 @@ In your team Cryp #1 has `Strike`, Cryp #2 has `Slay` and `Heal`, Cryp #3 has `S Cryp #2 -> Give Attack -> Attack Cryp #3 -> Give Stun -> Stun - Team Total (4 Red + 2 Basic gems) + Player Total (4 Red + 2 Basic gems) ### Round 2 @@ -87,12 +87,12 @@ In your team Cryp #1 has `Strike`, Cryp #2 has `Slay` and `Heal`, Cryp #3 has `S # Basic % GreenLife `Base` -> 5% inc hp -`Team Bonus` -> 3 basic gems -> +5% // 6 basic gems -> +10% // 12 basic gems -> +15% +`Player Bonus` -> 3 basic gems -> +5% // 6 basic gems -> +10% // 12 basic gems -> +15% Maximum 35% inc hp # Basic Speed `Base` -> 5% inc speed -`Team Bonus` -> 3 basic gems -> +10% // 6 basic gems -> +15% // 12 basic gems -> +20% +`Player Bonus` -> 3 basic gems -> +10% // 6 basic gems -> +15% // 12 basic gems -> +20% Maximum 50% inc speed # Basic Class Spec @@ -106,37 +106,37 @@ Generate by combining `Generic Spec (Basic Damage)` with respective RGB # Red Damage (Dmg + RR) Add 2 `red gems` `Base` -> 10% inc red dmg -`Team Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +25% +`Player Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +25% Maximum +60% red damage # Blue Damage (Dmg + BB) # Add 2 `blue gems` `Base` -> 10% inc blue dmg -`Team Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +25% +`Player Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +25% Maximum +60% blue damage # Healing (Dmg + GG) # Add 2 `green gems` `Base` -> 10% inc healing -`Team Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +25% +`Player Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +25% Maximum +60% inc healing # Red damage and healing (Dmg + RG) Add 1 red 1 green gem `Base` -> 5% inc red damage and 5% inc healing -`Team Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15% +`Player Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15% Maximum +35% inc red damage and 35% inc healing # Red and blue damage (Dmg + RB) Add 1 red and 1 blue gem `Base` -> 5% inc red damage and 5% inc healing -`Team Bonus` (2 red + 2 green gems) -> +5% + 5% // (5 red + 5 green gems) -> +10% + 10% % // 20 green gems -> +15% + 15% +`Player Bonus` (2 red + 2 green gems) -> +5% + 5% // (5 red + 5 green gems) -> +10% + 10% % // 20 green gems -> +15% + 15% Maximum +35% inc damage and 35% inc healing # Blue damage and healing (Dmg + BG) Add 1 blue and 1 green gem `Base` -> 5% inc blue damage and 5% inc healing -`Team Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15% +`Player Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15% Maximum +35% inc blue damage and 35% inc healing ### Increased GreenLife Combos ### @@ -146,37 +146,37 @@ Generate by combining `Generic Spec (Basic GreenLife)` with respective RGB # Increased % Red Life (Basic %HP + 2R) Add 2 `red gems` `Base` -> 10% inc red shield -`Team Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +20% +`Player Bonus` 5 red gems -> +10% // 10 red gems -> +15% // 20 red gems -> +20% Maximum +55% inc red shield # Increased % Red Life and GreenLife (Basic %HP + 1R1G) Add 1 red 1 green gem `Base` -> 5% inc red shield and 5% inc hp -`Team Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15% +`Player Bonus` (2R + 2G gems) -> +5% + 5% // (5R + 5G gems) -> +10% + 10% % // (10R + 10G) gems -> +15% + 15% Maximum +35% inc red shield and 35% inc hp # Increased % Blue Life (Basic %HP + 2B) Add 2 `blue gems` `Base` -> 10% inc red shield -`Team Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +20% +`Player Bonus` 5 blue gems -> +10% // 10 blue gems -> +15% // 20 blue gems -> +20% Maximum +55% inc blue shield # Increased % Blue Life and GreenLife (Basic %HP + 1B1G) Add `1 blue and 1 green gems` `Base` -> 5% inc red shield and 5% inc hp -`Team Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15% +`Player Bonus` (2B + 2G gems) -> +5% + 5% // (5B + 5G gems) -> +10% + 10% % // (10B + 10G) gems -> +15% + 15% Maximum +35% inc blue shield and 35% inc hp # Increased % GreenLife (Basic %HP + 2G) Add `2 green gems` `Base` -> 10% inc hp -`Team Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +20% +`Player Bonus` 5 green gems -> +10% // 10 green gems -> +15% // 20 green gems -> +20% Maximum +55% inc hp # Increased % Blue and Red Life (Basic %HP + 1B1R) Add `1 blue and 1 red gem` `Base` -> 5% inc red shield and 5% inc hp -`Team Bonus` (2B + 2R gems) -> +5% + 5% // (5B + 5R gems) -> +10% + 10% % // (10B + 10R) gems -> +15% + 15% +`Player Bonus` (2B + 2R gems) -> +5% + 5% // (5B + 5R gems) -> +10% + 10% % // (10B + 10R) gems -> +15% + 15% Maximum +35% inc blue shield and 35% inc red shield ## Upgraded Attack Spec Combos @@ -185,31 +185,31 @@ Maximum +35% inc blue shield and 35% inc red shield Cryp Requires `8 red gems` Adds `6 red gems` `Base` -> 15% increased strike damage -`Team Bonus` 15 red gems -> +15% // 20 red gems -> +20% // 30 red gems -> +30% +`Player Bonus` 15 red gems -> +15% // 20 red gems -> +20% // 30 red gems -> +30% Maximum 80% increased strike damage # Improved Heal (Combine Heal + Healing Spec x 2) Cryp Requires `8 green gems` `Base` -> 15% increased heal healing -`Team Bonus` 15 green gems -> +15% // 20 green gems -> +20% // 30 green gems -> +30% +`Player Bonus` 15 green gems -> +15% // 20 green gems -> +20% // 30 green gems -> +30% Maximum 80% increased heal healing # Increased Blast Damage (Combine Blast + Blue Spec x 2) Cryp Requires `8 blue gems` `Base` -> 15% increased blast damage -`Team Bonus` 15 blue gems -> +15% // 20 blue gems -> +20% // 30 blue gems -> +30% +`Player Bonus` 15 blue gems -> +15% // 20 blue gems -> +20% // 30 blue gems -> +30% Maximum 80% increased blast damage # Increased Slay Damage (Combine Slay + Red Damage Spec + Healing Spec) Cryp Requires `4 red 4 green gems` `Base` -> 15% increased slay damage -`Team Bonus` (8R + 8G) gems -> +15% // (10R + 10G) gems -> +20% // (15R + 15G) gems -> +30% +`Player Bonus` (8R + 8G) gems -> +15% // (10R + 10G) gems -> +20% // (15R + 15G) gems -> +30% Maximum 80% increased slay damage # Increased Banish Damage (Combine Slay + Red Damage Spec + Blue Damage Spec) Cryp Requires `4 red 4 blue gems` `Base` -> 15% increased slay damage -`Team Bonus` (8R + 8B) gems -> +15% // (10R + 10B) gems -> +20% // (15R + 15B) gems -> +30% +`Player Bonus` (8R + 8B) gems -> +15% // (10R + 10B) gems -> +20% // (15R + 15B) gems -> +30% Maximum 80% increased banish damage ## Other Combos @@ -217,7 +217,7 @@ Maximum 80% increased banish damage # Increased % Red Speed (Basic Speed + 2R) Add 2 red gems `Base` -> 15% inc red speed -`Team Bonus` 5 red gems -> +15% // 10 red gems -> +20% // 20 red gems -> +25% +`Player Bonus` 5 red gems -> +15% // 10 red gems -> +20% // 20 red gems -> +25% Maximum 80% inc red speed # Nature Affinity (Basic Class spec + 2R) diff --git a/server/WORKLOG.md b/server/WORKLOG.md index 22cece8f..ad8f3d16 100644 --- a/server/WORKLOG.md +++ b/server/WORKLOG.md @@ -16,14 +16,22 @@ # WORK WORK ## NOW -*INSTANCES* +*WARDEN* + +* games +check updated timestamps + once a second? +add a timestamp to each player + after 30s issue warning (client) + after 1m automove + increment warnings + after 3 warnings forfeit + +* instances + add timestamp to each player + after 60s force ready -lobby opens - add player - add player - players ready - on start -> vbox *CLIENT* * general @@ -76,6 +84,13 @@ make strike *really* hit first / resolve at same time? ## LATER * redis for game events + +* store instances / games in redis? + * not sure hwo to get sets of player games + * set joined_games_$account [game_id] + +* move player -> player + * chat * notifications * elo + leaderboards @@ -106,7 +121,7 @@ $$$ * empower on ko # Mechanic Ideas -teams +players 1v1 2v2 3v3 gem td style attr combinations diff --git a/server/src/cryp.rs b/server/src/cryp.rs index 2ca2c5b0..c58d5276 100644 --- a/server/src/cryp.rs +++ b/server/src/cryp.rs @@ -122,7 +122,7 @@ impl CrypStat { // self.recalculate(specs) // } - pub fn recalculate(&mut self, specs: &Vec, team_colours: &Colours) -> &mut CrypStat { + pub fn recalculate(&mut self, specs: &Vec, player_colours: &Colours) -> &mut CrypStat { let specs = specs .iter() .filter(|s| s.affects().contains(&self.stat)) @@ -131,7 +131,7 @@ impl CrypStat { // 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, team_colours)); + let value = specs.iter().fold(self.base, |acc, s| s.apply(acc, self.base, player_colours)); self.value = value; self.max = value; @@ -259,17 +259,17 @@ impl Cryp { self } - pub fn apply_modifiers(&mut self, team_colours: &Colours) -> &mut Cryp { + pub fn apply_modifiers(&mut self, player_colours: &Colours) -> &mut Cryp { self.specs.sort_unstable(); - self.red_damage.recalculate(&self.specs, team_colours); - self.red_life.recalculate(&self.specs, team_colours); - self.blue_damage.recalculate(&self.specs, team_colours); - self.blue_life.recalculate(&self.specs, team_colours); - self.evasion.recalculate(&self.specs, team_colours); - self.speed.recalculate(&self.specs, team_colours); - self.green_damage.recalculate(&self.specs, team_colours); - self.green_life.recalculate(&self.specs, team_colours); + self.red_damage.recalculate(&self.specs, player_colours); + self.red_life.recalculate(&self.specs, player_colours); + self.blue_damage.recalculate(&self.specs, player_colours); + self.blue_life.recalculate(&self.specs, player_colours); + self.evasion.recalculate(&self.specs, player_colours); + self.speed.recalculate(&self.specs, player_colours); + self.green_damage.recalculate(&self.specs, player_colours); + self.green_life.recalculate(&self.specs, player_colours); self } @@ -278,6 +278,11 @@ impl Cryp { self.green_life.value == 0 } + pub fn force_ko(&mut self) -> &mut Cryp { + 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() { @@ -836,21 +841,21 @@ mod tests { } #[test] - fn cryp_team_modifiers_test() { + fn cryp_player_modifiers_test() { let mut cryp = Cryp::new() - .named(&"team player".to_string()); + .named(&"player player".to_string()); cryp.spec_add(Spec::RedDamageI).unwrap(); cryp.spec_add(Spec::GreenDamageI).unwrap(); cryp.spec_add(Spec::BlueDamageI).unwrap(); - let team_colours = Colours { + let player_colours = Colours { red: 5, green: 15, blue: 25, }; - cryp.apply_modifiers(&team_colours); + cryp.apply_modifiers(&player_colours); assert!(cryp.red_damage.value == cryp.red_damage.base + cryp.red_damage.base.pct(20)); assert!(cryp.green_damage.value == cryp.green_damage.base + cryp.green_damage.base.pct(40)); diff --git a/server/src/game.rs b/server/src/game.rs index 3bfca93b..93e73327 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -1,6 +1,10 @@ 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; @@ -14,110 +18,51 @@ use skill::{Skill, Effect, Cast, Resolution, Event, resolve}; use player::{Player}; use instance::{instance_game_finished, global_game_finished}; -#[derive(Debug,Clone,Serialize,Deserialize)] -pub struct Team { - pub id: Uuid, - pub player: Option, - pub bot: bool, - pub cryps: Vec, -} - -impl Team { - pub fn new(account: Uuid) -> Team { - return Team { - id: account, - player: None, - cryps: vec![], - bot: false, - }; - } - - pub fn set_bot(&mut self) -> &mut Team { - self.bot = true; - self - } - - fn skills_required(&self) -> usize { - let required = self.cryps.iter() - .filter(|c| !c.is_ko()) - .filter(|c| c.available_skills().len() > 0) - .collect::>().len(); - // println!("{:} requires {:} skills this turn", self.id, required); - return required; - } - - fn taunting(&self) -> Option<&Cryp> { - self.cryps.iter() - .find(|c| c.affected(Effect::Taunt)) - } - - pub fn set_cryps(&mut self, mut cryps: Vec) -> &mut Team { - cryps.sort_unstable_by_key(|c| c.id); - self.cryps = cryps; - self - } - - pub fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> { - self.cryps.iter_mut().find(|c| c.id == id) - } -} - #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] pub enum Phase { Start, Skill, - Target, Resolve, Finish, } -#[derive(Debug,Clone,Copy,Serialize,Deserialize)] -pub enum GameMode { - Normal, - Pvp, - Zone3v2Attack, - Zone2v2Caster, - Zone3v3MeleeMiniboss, - Zone3v3HealerBoss, -} - #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Game { pub id: Uuid, - pub team_size: usize, - pub team_num: usize, - pub teams: Vec, + pub player_cryps: usize, + pub player_num: usize, + pub players: Vec, pub phase: Phase, pub stack: Vec, pub resolved: Vec, pub log: Vec, pub instance: Option, - pub mode: GameMode, + phase_start: DateTime, } impl Game { pub fn new() -> Game { return Game { id: Uuid::new_v4(), - team_size: 0, - team_num: 0, - teams: vec![], + player_cryps: 0, + player_num: 0, + players: vec![], phase: Phase::Start, stack: vec![], resolved: vec![], log: vec![], instance: None, - mode: GameMode::Normal, + phase_start: Utc::now(), }; } - pub fn set_team_num(&mut self, size: usize) -> &mut Game { - self.team_num = size; + pub fn set_player_num(&mut self, size: usize) -> &mut Game { + self.player_num = size; self } - pub fn set_team_size(&mut self, size: usize) -> &mut Game { - self.team_size = size; + pub fn set_player_cryps(&mut self, size: usize) -> &mut Game { + self.player_cryps = size; self } @@ -126,56 +71,52 @@ impl Game { self } - pub fn set_mode(&mut self, mode: GameMode) -> &mut Game { - self.mode = mode; - self - } - pub fn joinable(&self) -> bool { self.can_start() } - pub fn team_add(&mut self, team: Team) -> Result<&mut Game, Error> { - if self.teams.len() == self.team_num { - return Err(err_msg("maximum number of teams")); + 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.teams.iter().any(|t| t.id == team.id) { - return Err(err_msg("team already in game")); + if self.players.iter().any(|t| t.id == player.id) { + return Err(err_msg("player already in game")); } - if team.cryps.iter().all(|c| c.skills.len() == 0) { - return Err(err_msg("your cryps have no skills")); + if player.cryps.iter().all(|c| c.skills.len() == 0) { + println!("WARNING: {:?} has no skills and has forfeited {:?}", player.name, self.id); + player.forfeit(); } - let team_description = team.cryps.iter().map(|c| c.name.clone()).collect::>().join(", "); - self.log.push(format!("{:} has joined the game.", team_description)); + let player_description = player.cryps.iter().map(|c| c.name.clone()).collect::>().join(", "); + self.log.push(format!("{:} has joined the game. [{:?}]", player.name, player_description)); - self.teams.push(team); + self.players.push(player); Ok(self) } - // handle missing team properly - pub fn team_by_id(&mut self, id: Uuid) -> &mut Team { - match self.teams.iter_mut().find(|t| t.id == id) { - Some(t) => t, - None => panic!("id not in game {:}", id), - } + // 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 cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> { - match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == id)) { - Some(team) => team.cryps.iter_mut().find(|c| c.id == id), + match self.players.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == id)) { + Some(player) => player.cryps.iter_mut().find(|c| c.id == id), None => None, } } pub fn cryp_by_id_take(&mut self, id: Uuid) -> Cryp { - match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == id)) { - Some(team) => { - let i = team.cryps.iter().position(|c| c.id == id).unwrap(); - team.cryps.remove(i) + match self.players.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == id)) { + Some(player) => { + let i = player.cryps.iter().position(|c| c.id == id).unwrap(); + player.cryps.remove(i) } None => panic!("id not in game {:}", id), } @@ -183,7 +124,7 @@ impl Game { fn all_cryps(&self) -> Vec { - self.teams.clone() + self.players.clone() .into_iter() .flat_map( |t| t.cryps @@ -192,12 +133,12 @@ impl Game { } fn update_cryp(&mut self, cryp: &mut Cryp) -> &mut Game { - match self.teams.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == cryp.id)) { - Some(team) => { - let index = team.cryps.iter().position(|t| t.id == cryp.id).unwrap(); - team.cryps.remove(index); - team.cryps.push(cryp.clone()); - team.cryps.sort_unstable_by_key(|c| c.id); + match self.players.iter_mut().find(|t| t.cryps.iter().any(|c| c.id == cryp.id)) { + Some(player) => { + let index = player.cryps.iter().position(|t| t.id == cryp.id).unwrap(); + player.cryps.remove(index); + player.cryps.push(cryp.clone()); + player.cryps.sort_unstable_by_key(|c| c.id); }, None => panic!("cryp not in game"), }; @@ -205,17 +146,31 @@ impl Game { self } - fn can_start(&self) -> bool { - return self.teams.len() == self.team_num - && self.teams.iter().all(|t| t.cryps.len() == self.team_size) + pub fn can_start(&self) -> bool { + return self.players.len() == self.player_num + && self.players.iter().all(|t| t.cryps.len() == self.player_cryps) } pub fn start(mut self) -> Game { self.log.push("Game starting...".to_string()); + + // forfeit + if self.finished() { + return self.finish(); + } + self.skill_phase_start() } fn skill_phase_start(mut self) -> Game { + self.phase_start = Utc::now(); + + for player in self.players.iter_mut() { + if player.skills_required() != 0 { + player.set_ready(false); + } + } + self.log.push("".to_string()); if ![Phase::Start, Phase::Resolve].contains(&self.phase) { @@ -225,6 +180,7 @@ impl Game { self.phase = Phase::Skill; self.pve_add_skills(); + if self.skill_phase_finished() { return self.resolve_phase_start() } @@ -235,10 +191,10 @@ impl Game { fn pve_add_skills(&mut self) -> &mut Game { let mut pve_skills = vec![]; - for mobs in self.teams + for mobs in self.players .iter() .filter(|t| t.bot) { - let player_team = self.teams.iter().find(|t| t.id != mobs.id).unwrap(); + let player_player = self.players.iter().find(|t| t.id != mobs.id).unwrap(); for mob in mobs.cryps.iter() { let skill = mob.mob_select_skill(); @@ -252,7 +208,7 @@ impl Game { let mut find_target = || { match s.defensive() { true => &mobs.cryps[rng.gen_range(0, mobs.cryps.len())], - false => &player_team.cryps[rng.gen_range(0, player_team.cryps.len())], + false => &player_player.cryps[rng.gen_range(0, player_player.cryps.len())], } }; @@ -269,20 +225,25 @@ impl Game { } } - for (team_id, mob_id, target_id, s) in pve_skills { - match self.add_skill(team_id, mob_id, target_id, s) { + 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) => { println!("{:?}", self.cryp_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, team_id: Uuid, source_cryp_id: Uuid, target_cryp_id: Option, skill: Skill) -> Result { + fn add_skill(&mut self, player_id: Uuid, source_cryp_id: Uuid, target_cryp_id: Option, skill: Skill) -> Result { + // check player in game + self.player_by_id(player_id)?; + if self.phase != Phase::Skill { return Err(err_msg("game not in skill phase")); } @@ -339,22 +300,34 @@ impl Game { self.stack.remove(s); } - let skill = Cast::new(source_cryp_id, team_id, final_target_id, skill); + let skill = Cast::new(source_cryp_id, player_id, final_target_id, skill); let skill_id = skill.id; self.stack.push(skill); return Ok(skill_id); } + 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.teams.iter() - // for every team - .all(|t| self.stack.iter() - // the number of skills they have cast - .filter(|s| s.source_team_id == t.id).collect::>() - // should equal the number required this turn - .len() == t.skills_required() - ) + 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 @@ -388,7 +361,7 @@ impl Game { } fn cryp_aoe_targets(&self, cryp_id: Uuid) -> Vec { - self.teams.iter() + self.players.iter() .find(|t| t.cryps.iter().any(|c| c.id == cryp_id)) .unwrap() .cryps @@ -398,11 +371,11 @@ impl Game { } fn get_targets(&self, skill: Skill, source: &Cryp, target_cryp_id: Uuid) -> Vec { - let target_team = self.teams.iter() + let target_player = self.players.iter() .find(|t| t.cryps.iter().any(|c| c.id == target_cryp_id)) .unwrap(); - if let Some(t) = target_team.taunting() { + if let Some(t) = target_player.taunting() { return vec![t.id]; } @@ -566,11 +539,11 @@ impl Game { } pub fn finished(&self) -> bool { - self.teams.iter().any(|t| t.cryps.iter().all(|c| c.is_ko())) + self.players.iter().any(|t| t.cryps.iter().all(|c| c.is_ko())) } - pub fn winner(&self) -> Option<&Team> { - self.teams.iter().find(|t| t.cryps.iter().any(|c| !c.is_ko())) + pub fn winner(&self) -> Option<&Player> { + self.players.iter().find(|t| t.cryps.iter().any(|c| !c.is_ko())) } fn finish(mut self) -> Game { @@ -578,82 +551,50 @@ impl Game { self.log.push(format!("Game finished.")); { - let winner = self.teams.iter().find(|t| t.cryps.iter().any(|c| !c.is_ko())); + let winner = self.players.iter().find(|t| t.cryps.iter().any(|c| !c.is_ko())); match winner { - Some(w) => self.log.push(format!("Winner: {:}", w.id)), + Some(w) => self.log.push(format!("Winner: {:}", w.name)), None => self.log.push(format!("Game was drawn.")), }; } self } -} -pub fn game_skill(params: GameSkillParams, tx: &mut Transaction, account: &Account) -> Result { - let query = " - SELECT * - FROM games - WHERE id = $1 - "; - - let result = tx - .query(query, &[¶ms.game_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 cryp - let game_bytes: Vec = returned.get("data"); - let mut game = from_slice::(&game_bytes)?; - - if game.phase != Phase::Skill { - return Err(err_msg("game not in skill phase")) + fn phase_timed_out(&self) -> bool { + Utc::now().signed_duration_since(self.phase_start).num_seconds() > 60 } - game.add_skill(account.id, params.cryp_id, params.target_cryp_id, params.skill)?; + pub fn upkeep(mut self) -> Game { + if self.phase == Phase::Finish { + return self; + } - if game.skill_phase_finished() { - game = game.resolve_phase_start(); + println!("upkeep beginning: {:?} vs {:?}", self.players[0].name, self.players[1].name); + + if !self.phase_timed_out() { + return self; + } + + for player in self.players.iter_mut() { + if !player.ready { + player.set_ready(true); + player.add_warning(); + println!("upkeep: {:?} warned", player.name); + if player.warnings >= 3 { + player.forfeit(); + println!("upkeep: {:?} forfeited", player.name); + self.log.push(format!("{:?} forfeited.", player.name)); + } + } + } + + self = self.resolve_phase_start(); + self } - - game_update(&game, tx)?; - - Ok(game) } -// pub fn game_target(params: GameTargetParams, tx: &mut Transaction, account: &Account) -> Result { -// let query = " -// SELECT * -// FROM games -// WHERE id = $1 -// "; - -// let result = tx -// .query(query, &[¶ms.game_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 cryp -// let game_bytes: Vec = returned.get("data"); -// let mut game = from_slice::(&game_bytes)?; - -// game.add_target(account.id, params.cryp_id, params.skill_id)?; - -// if game.target_phase_finished() { -// game.resolve_phase_start(); -// } - -// game_update(game, tx)?; - -// Ok(game) -// } - -pub fn game_write(game: &Game, tx: &mut Transaction) -> Result<(), Error> { +pub fn game_write(tx: &mut Transaction, game: &Game) -> Result<(), Error> { let game_bytes = to_vec(&game)?; let query = " @@ -681,6 +622,7 @@ pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result { SELECT * FROM games WHERE id = $1 + FOR UPDATE; "; let result = tx @@ -698,103 +640,155 @@ pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result { return Ok(game); } -pub fn game_global_startup(tx: &mut Transaction) -> Result<(), Error> { - if game_global_get(tx).is_ok() { - println!("global mm game exists"); - return Ok(()); +pub fn games_need_upkeep(tx: &mut Transaction) -> Result, Error> { + // let query = " + // SELECT data, id + // FROM games + // WHERE updated_at < now() - interval '5 seconds' + // AND finished = false; + // "; + + let query = " + SELECT data, id + FROM games + WHERE 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) => { + game_delete(tx, id)?; + } + }; } - let mut game = Game::new(); - - game - .set_team_num(2) - .set_team_size(3) - .set_mode(GameMode::Pvp); - - game_write(&game, tx)?; + return Ok(list); +} +pub fn game_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { let query = " - INSERT INTO matchmaking (id, game) - VALUES ($1, $2) - RETURNING id; + DELETE + FROM games + WHERE id = $1; "; let result = tx - .query(query, &[&Uuid::nil(), &game.id])?; + .execute(query, &[&id])?; - result.iter().next().ok_or(format_err!("no game written"))?; + if result != 1 { + return Err(format_err!("unable to delete player {:?}", id)); + } - println!("{:} wrote global mm startup", game.id); + println!("game deleted {:?}", id); return Ok(()); } -pub fn game_global_set(tx: &mut Transaction, game: &Game) -> Result<(), Error> { - let query = " - UPDATE matchmaking - SET game = $1 - WHERE id = $2 - RETURNING id, game; - "; +// pub fn game_global_startup(tx: &mut Transaction) -> Result<(), Error> { +// if game_global_get(tx).is_ok() { +// println!("global mm game exists"); +// return Ok(()); +// } - let result = tx - .query(query, &[&game.id, &Uuid::nil()])?; +// let mut game = Game::new(); - result.iter() - .next() - .ok_or(err_msg("could not set global game mm"))?; +// game +// .set_player_num(2) +// .set_player_cryps(3) +// .set_mode(GameMode::Pvp); - return Ok(()); -} +// game_write(tx, &game)?; -pub fn game_global_get(tx: &mut Transaction) -> Result { - let query = " - SELECT * from games - WHERE id = ( - SELECT game - FROM matchmaking - WHERE id = $1 - ); - "; +// let query = " +// INSERT INTO matchmaking (id, game) +// VALUES ($1, $2) +// RETURNING id; +// "; - let delete_query = " - DELETE from matchmaking; - "; +// let result = tx +// .query(query, &[&Uuid::nil(), &game.id])?; - let result = tx - .query(query, &[&Uuid::nil()])?; +// result.iter().next().ok_or(format_err!("no game written"))?; - let returned = match result.iter().next() { - Some(row) => row, - None => return Err(err_msg("game not found")), - }; +// println!("{:} wrote global mm startup", game.id); - // tells from_slice to cast into a cryp - let game_bytes: Vec = returned.get("data"); - let game = match from_slice::(&game_bytes) { - Ok(g) => g, - Err(_) => { - tx.query(delete_query, &[])?; - return Err(err_msg("matchmaking game was invalid")) - } - }; +// return Ok(()); +// } - return Ok(game); -} +// pub fn game_global_set(tx: &mut Transaction, game: &Game) -> Result<(), Error> { +// let query = " +// UPDATE matchmaking +// SET game = $1 +// WHERE id = $2 +// RETURNING id, game; +// "; + +// let result = tx +// .query(query, &[&game.id, &Uuid::nil()])?; + +// result.iter() +// .next() +// .ok_or(err_msg("could not set global game mm"))?; + +// return Ok(()); +// } + +// pub fn game_global_get(tx: &mut Transaction) -> Result { +// let query = " +// SELECT * from games +// WHERE id = ( +// SELECT game +// FROM matchmaking +// WHERE id = $1 +// ); +// "; + +// let delete_query = " +// DELETE from matchmaking; +// "; + +// let result = tx +// .query(query, &[&Uuid::nil()])?; + +// let returned = match result.iter().next() { +// Some(row) => row, +// None => return Err(err_msg("game not found")), +// }; + +// // tells from_slice to cast into a cryp +// let game_bytes: Vec = returned.get("data"); +// let game = match from_slice::(&game_bytes) { +// Ok(g) => g, +// Err(_) => { +// tx.query(delete_query, &[])?; +// return Err(err_msg("matchmaking game was invalid")) +// } +// }; + +// return Ok(game); +// } -pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { +pub fn game_update(tx: &mut Transaction, game: &Game) -> Result<(), Error> { let game_bytes = to_vec(&game)?; let query = " UPDATE games - SET data = $1, updated_at = now() - WHERE id = $2 + SET data = $1, finished = $2, updated_at = now() + WHERE id = $3 RETURNING id, data; "; let result = tx - .query(query, &[&game_bytes, &game.id])?; + .query(query, &[&game_bytes, &game.finished(), &game.id])?; result.iter().next().ok_or(format_err!("game {:?} could not be written", game))?; @@ -810,6 +804,34 @@ pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { return Ok(()); } +pub fn game_skill(params: GameSkillParams, tx: &mut Transaction, account: &Account) -> Result { + let mut game = game_get(tx, params.game_id)?; + + game.add_skill(account.id, params.cryp_id, params.target_cryp_id, params.skill)?; + + if game.skill_phase_finished() { + game = game.resolve_phase_start(); + } + + game_update(tx, &game)?; + + Ok(game) +} + +pub fn game_ready(params: GameStateParams, tx: &mut Transaction, account: &Account) -> Result { + let mut game = game_get(tx, params.id)?; + + game.player_ready(account.id)?; + + if game.skill_phase_finished() { + game = game.resolve_phase_start(); + } + + game_update(tx, &game)?; + + Ok(game) +} + // pub fn game_pve_new(cryp_ids: Vec, mode: GameMode, tx: &mut Transaction, account: &Account) -> Result { // if cryp_ids.len() == 0 { // return Err(err_msg("no cryps selected")); @@ -821,7 +843,7 @@ pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { // .collect::, Error>>()?; // if cryps.len() > 3 { -// return Err(err_msg("team size too large (3 max)")); +// return Err(err_msg("player size too large (3 max)")); // } // // create the game @@ -829,22 +851,22 @@ pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { // // let game_id = game.id; // game; -// .set_team_num(2) -// .set_team_size(cryps.len()) +// .set_player_num(2) +// .set_player_cryps(cryps.len()) // .set_mode(mode); -// // create the mob team -// let mob_team = generate_mob_team(mode, &cryps); +// // create the mob player +// let mob_player = generate_mob_player(mode, &cryps); // // add the players -// let mut plr_team = Team::new(account.id); -// plr_team +// let mut plr_player = Player::new(account.id); +// plr_player // .set_cryps(cryps); // game -// .team_add(plr_team)? -// .team_add(mob_team)?; +// .player_add(plr_player)? +// .player_add(mob_player)?; // game.start(); @@ -855,7 +877,7 @@ pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { // let game = game_pve_new(params.cryp_ids, GameMode::Normal, tx, account)?; // // persist -// game_write(&game, tx)?; +// game_write(tx, &game)?; // Ok(game) // } @@ -866,16 +888,13 @@ pub fn game_instance_new(tx: &mut Transaction, players: Vec, game_id: Uu game.id = game_id; game - .set_team_num(2) - .set_team_size(3) - .set_instance(instance_id) - .set_mode(GameMode::Pvp); + .set_player_num(2) + .set_player_cryps(3) + .set_instance(instance_id); - // create the initiators team + // create the initiators player for player in players { - let mut team = Team::new(player.id); - team.set_cryps(player.cryps); - game.team_add(team)?; + game.player_add(player)?; } if game.can_start() { @@ -883,7 +902,7 @@ pub fn game_instance_new(tx: &mut Transaction, players: Vec, game_id: Uu } // persist - game_write(&game, tx)?; + game_write(tx, &game)?; Ok(game) } @@ -891,9 +910,7 @@ pub fn game_instance_new(tx: &mut Transaction, players: Vec, game_id: Uu pub fn game_instance_join(tx: &mut Transaction, player: Player, game_id: Uuid) -> Result { let mut game = game_get(tx, game_id)?; - let mut team = Team::new(player.id); - team.set_cryps(player.cryps); - game.team_add(team)?; + game.player_add(player)?; if game.can_start() { game = game.start(); @@ -901,7 +918,7 @@ pub fn game_instance_join(tx: &mut Transaction, player: Player, game_id: Uuid) - println!("{:?} game joined", game.id); - game_update(&game, tx)?; + game_update(tx, &game)?; Ok(game) } @@ -939,24 +956,20 @@ mod tests { let mut game = Game::new(); game - .set_team_num(2) - .set_team_size(1); + .set_player_num(2) + .set_player_cryps(1); - let x_team_id = Uuid::new_v4(); - x.account = x_team_id; - let mut x_team = Team::new(x_team_id); - x_team - .set_cryps(vec![x]); + let x_player_id = Uuid::new_v4(); + x.account = x_player_id; + let mut x_player = Player::new(x_player_id, &"ntr".to_string(), vec![x]); - let y_team_id = Uuid::new_v4(); - y.account = y_team_id; - let mut y_team = Team::new(y_team_id); - y_team - .set_cryps(vec![y]); + let y_player_id = Uuid::new_v4(); + y.account = y_player_id; + let mut y_player = Player::new(y_player_id, &"mash".to_string(), vec![y]); game - .team_add(x_team).unwrap() - .team_add(y_team).unwrap(); + .player_add(x_player).unwrap() + .player_add(y_player).unwrap(); assert!(game.can_start()); @@ -987,26 +1000,22 @@ mod tests { let mut game = Game::new(); game - .set_team_num(2) - .set_team_size(2); + .set_player_num(2) + .set_player_cryps(2); - let i_team_id = Uuid::new_v4(); - i.account = i_team_id; - j.account = i_team_id; - let mut i_team = Team::new(i_team_id); - i_team - .set_cryps(vec![i,j]); + 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_team_id = Uuid::new_v4(); - x.account = x_team_id; - y.account = x_team_id; - let mut x_team = Team::new(x_team_id); - x_team - .set_cryps(vec![x,y]); + 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 - .team_add(i_team).unwrap() - .team_add(x_team).unwrap(); + .player_add(i_player).unwrap() + .player_add(x_player).unwrap(); assert!(game.can_start()); @@ -1017,14 +1026,17 @@ mod tests { fn phase_test() { let mut game = create_test_game(); - let x_team = game.teams[0].clone(); - let y_team = game.teams[1].clone(); + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = y_team.cryps[0].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = y_player.cryps[0].clone(); - game.add_skill(x_team.id, x_cryp.id, Some(y_cryp.id), Skill::Attack).unwrap(); - game.add_skill(y_team.id, y_cryp.id, Some(x_cryp.id), Skill::Attack).unwrap(); + game.add_skill(x_player.id, x_cryp.id, Some(y_cryp.id), Skill::Attack).unwrap(); + game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::Attack).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); assert!(game.skill_phase_finished()); @@ -1039,14 +1051,17 @@ mod tests { fn stun_test() { let mut game = create_test_game(); - let x_team = game.teams[0].clone(); - let y_team = game.teams[1].clone(); + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = y_team.cryps[0].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = y_player.cryps[0].clone(); - let _x_stun_id = game.add_skill(x_team.id, x_cryp.id, Some(y_cryp.id), Skill::TestStun).unwrap(); - game.add_skill(y_team.id, y_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + let _x_stun_id = game.add_skill(x_player.id, x_cryp.id, Some(y_cryp.id), Skill::TestStun).unwrap(); + game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::TestTouch).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(); @@ -1054,34 +1069,37 @@ mod tests { // should auto progress back to skill phase assert!(game.phase == Phase::Skill); - // assert!(game.team_by_id(y_team.id).cryps[0].is_stunned()); - // assert!(game.team_by_id(y_team.id).skills_required() == 0); + // assert!(game.player_by_id(y_player.id).cryps[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_team = game.teams[0].clone(); - let y_team = game.teams[1].clone(); + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = y_team.cryps[0].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = y_player.cryps[0].clone(); - game.team_by_id(y_team.id).cryp_by_id(y_cryp.id).unwrap().red_damage.force(u64::max_value()); - game.team_by_id(y_team.id).cryp_by_id(y_cryp.id).unwrap().speed.force(u64::max_value()); + game.player_by_id(y_player.id).unwrap().cryp_by_id(y_cryp.id).unwrap().red_damage.force(u64::max_value()); + game.player_by_id(y_player.id).unwrap().cryp_by_id(y_cryp.id).unwrap().speed.force(u64::max_value()); // just in case // remove all mitigation - game.team_by_id(x_team.id).cryp_by_id(x_cryp.id).unwrap().red_life.force(0); + game.player_by_id(x_player.id).unwrap().cryp_by_id(x_cryp.id).unwrap().red_life.force(0); - let _x_stun_id = game.add_skill(x_team.id, x_cryp.id, Some(y_cryp.id), Skill::TestStun).unwrap(); - game.add_skill(y_team.id, y_cryp.id, Some(x_cryp.id), Skill::Attack).unwrap(); + let _x_stun_id = game.add_skill(x_player.id, x_cryp.id, Some(y_cryp.id), Skill::TestStun).unwrap(); + game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.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.team_by_id(y_team.id).cryps[0].is_stunned()); + assert!(!game.player_by_id(y_player.id).unwrap().cryps[0].is_stunned()); assert!(game.phase == Phase::Finish); } @@ -1089,71 +1107,80 @@ mod tests { fn cooldown_test() { let mut game = create_test_game(); - let x_team = game.teams[0].clone(); - let y_team = game.teams[1].clone(); + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = y_team.cryps[0].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = y_player.cryps[0].clone(); // should auto progress back to skill phase assert!(game.phase == Phase::Skill); - assert!(game.team_by_id(y_team.id).cryps[0].skill_on_cd(Skill::Block).is_none()); - assert!(game.team_by_id(y_team.id).cryps[0].skill_on_cd(Skill::Stun).is_some()); - assert!(game.team_by_id(x_team.id).cryps[0].skill_on_cd(Skill::Block).is_none()); + assert!(game.player_by_id(y_player.id).unwrap().cryps[0].skill_on_cd(Skill::Block).is_none()); + assert!(game.player_by_id(y_player.id).unwrap().cryps[0].skill_on_cd(Skill::Stun).is_some()); + assert!(game.player_by_id(x_player.id).unwrap().cryps[0].skill_on_cd(Skill::Block).is_none()); - let _x_stun_id = game.add_skill(x_team.id, x_cryp.id, Some(y_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(y_team.id, y_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_player.id, x_cryp.id, Some(y_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::TestTouch).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.team_by_id(y_team.id).cryps[0].skill_on_cd(Skill::Stun).is_none()); + assert!(game.player_by_id(y_player.id).unwrap().cryps[0].skill_on_cd(Skill::Stun).is_none()); // second round // now we block and it should go back on cd - let _x_block_id = game.add_skill(x_team.id, x_cryp.id, Some(y_cryp.id), Skill::Stun).unwrap(); - let _y_touch_id = game.add_skill(y_team.id, y_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + let _x_block_id = game.add_skill(x_player.id, x_cryp.id, Some(y_cryp.id), Skill::Stun).unwrap(); + let _y_touch_id = game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); game = game.resolve_phase_start(); - assert!(game.team_by_id(x_team.id).cryps[0].skill_on_cd(Skill::Stun).is_some()); - assert!(game.team_by_id(y_team.id).cryps[0].skill_on_cd(Skill::Block).is_none()); + assert!(game.player_by_id(x_player.id).unwrap().cryps[0].skill_on_cd(Skill::Stun).is_some()); + assert!(game.player_by_id(y_player.id).unwrap().cryps[0].skill_on_cd(Skill::Block).is_none()); } #[test] fn parry_test() { let mut game = create_test_game(); - let x_team = game.teams[0].clone(); - let y_team = game.teams[1].clone(); + let x_player = game.players[0].clone(); + let y_player = game.players[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = y_team.cryps[0].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = y_player.cryps[0].clone(); - let _x_block_id = game.add_skill(x_team.id, x_cryp.id, None, Skill::TestParry).unwrap(); - game.add_skill(y_team.id, y_cryp.id, Some(x_cryp.id), Skill::TestStun).unwrap(); + let _x_block_id = game.add_skill(x_player.id, x_cryp.id, None, Skill::TestParry).unwrap(); + game.add_skill(y_player.id, y_cryp.id, Some(x_cryp.id), Skill::TestStun).unwrap(); + + game.player_ready(x_player.id).unwrap(); + game.player_ready(y_player.id).unwrap(); game = game.resolve_phase_start(); // should not be stunned because of parry - assert!(game.team_by_id(x_team.id).cryps[0].is_stunned() == false); + assert!(game.player_by_id(x_player.id).unwrap().cryps[0].is_stunned() == false); // riposte - assert!(game.team_by_id(y_team.id).cryps[0].green_life() == 768); + assert!(game.player_by_id(y_player.id).unwrap().cryps[0].green_life() == 768); } #[test] fn aoe_test() { let mut game = create_2v2_test_game(); - let i_team = game.teams[0].clone(); - let x_team = game.teams[1].clone(); + let i_player = game.players[0].clone(); + let x_player = game.players[1].clone(); - let i_cryp = i_team.cryps[0].clone(); - let j_cryp = i_team.cryps[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = x_team.cryps[1].clone(); + let i_cryp = i_player.cryps[0].clone(); + let j_cryp = i_player.cryps[1].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = x_player.cryps[1].clone(); game.cryp_by_id(x_cryp.id).unwrap().learn_mut(Skill::Ruin); @@ -1161,10 +1188,13 @@ mod tests { game.cryp_by_id(x_cryp.id).unwrap().reduce_cooldowns(); } - game.add_skill(i_team.id, i_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(i_team.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, x_cryp.id, Some(i_cryp.id), Skill::Ruin).unwrap(); - game.add_skill(x_team.id, y_cryp.id, Some(i_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(i_player.id, i_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(i_player.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_player.id, x_cryp.id, Some(i_cryp.id), Skill::Ruin).unwrap(); + game.add_skill(x_player.id, y_cryp.id, Some(i_cryp.id), Skill::TestTouch).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(); @@ -1194,13 +1224,13 @@ mod tests { fn taunt_test() { let mut game = create_2v2_test_game(); - let i_team = game.teams[0].clone(); - let x_team = game.teams[1].clone(); + let i_player = game.players[0].clone(); + let x_player = game.players[1].clone(); - let i_cryp = i_team.cryps[0].clone(); - let j_cryp = i_team.cryps[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = x_team.cryps[1].clone(); + let i_cryp = i_player.cryps[0].clone(); + let j_cryp = i_player.cryps[1].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = x_player.cryps[1].clone(); game.cryp_by_id(x_cryp.id).unwrap().learn_mut(Skill::Taunt); @@ -1208,12 +1238,14 @@ mod tests { game.cryp_by_id(x_cryp.id).unwrap().reduce_cooldowns(); } - game.add_skill(i_team.id, i_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(i_team.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, x_cryp.id, Some(i_cryp.id), Skill::Taunt).unwrap(); - game.add_skill(x_team.id, y_cryp.id, Some(i_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(i_player.id, i_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(i_player.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_player.id, x_cryp.id, Some(i_cryp.id), Skill::Taunt).unwrap(); + game.add_skill(x_player.id, y_cryp.id, Some(i_cryp.id), Skill::TestTouch).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(); assert!(game.resolved.len() == 4); @@ -1229,18 +1261,21 @@ mod tests { fn ko_pve_test() { let mut game = create_2v2_test_game(); - let i_team = game.teams[0].clone(); - let x_team = game.teams[1].clone(); + let i_player = game.players[0].clone(); + let x_player = game.players[1].clone(); - let i_cryp = i_team.cryps[0].clone(); - let j_cryp = i_team.cryps[1].clone(); - let x_cryp = x_team.cryps[0].clone(); - let y_cryp = x_team.cryps[1].clone(); + let i_cryp = i_player.cryps[0].clone(); + let j_cryp = i_player.cryps[1].clone(); + let x_cryp = x_player.cryps[0].clone(); + let y_cryp = x_player.cryps[1].clone(); - game.add_skill(i_team.id, i_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(i_team.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, x_cryp.id, Some(i_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, y_cryp.id, Some(i_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(i_player.id, i_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(i_player.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_player.id, x_cryp.id, Some(i_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_player.id, y_cryp.id, Some(i_cryp.id), Skill::TestTouch).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(); @@ -1248,22 +1283,34 @@ mod tests { assert!([Phase::Skill, Phase::Finish].contains(&game.phase)); // kill a cryp - game.team_by_id(i_team.id).cryp_by_id(i_cryp.id).unwrap().green_life.reduce(u64::max_value()); + game.player_by_id(i_player.id).unwrap().cryp_by_id(i_cryp.id).unwrap().green_life.reduce(u64::max_value()); - assert!(game.team_by_id(i_team.id).skills_required() == 1); - assert!(game.team_by_id(x_team.id).skills_required() == 2); + 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_team.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, x_cryp.id, Some(j_cryp.id), Skill::TestTouch).unwrap(); - game.add_skill(x_team.id, y_cryp.id, Some(j_cryp.id), Skill::TestTouch).unwrap(); - assert!(game.add_skill(x_team.id, x_cryp.id, Some(i_cryp.id), Skill::TestTouch).is_err()); + game.add_skill(i_player.id, j_cryp.id, Some(x_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_player.id, x_cryp.id, Some(j_cryp.id), Skill::TestTouch).unwrap(); + game.add_skill(x_player.id, y_cryp.id, Some(j_cryp.id), Skill::TestTouch).unwrap(); + assert!(game.add_skill(x_player.id, x_cryp.id, Some(i_cryp.id), Skill::TestTouch).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.team_by_id(i_team.id).skills_required() == 1); - assert!(game.team_by_id(x_team.id).skills_required() == 2); + 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 upkeep_test() { + let mut game = create_2v2_test_game(); + game.players[0].set_ready(true); + game.phase_start = Utc::now().checked_sub_signed(Duration::seconds(61)).unwrap(); + game = game.upkeep(); + assert!(game.players[1].warnings == 1); + } } diff --git a/server/src/instance.rs b/server/src/instance.rs index 8c97d6a7..eca9ffb6 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -9,12 +9,16 @@ use failure::err_msg; use std::iter; +// timekeeping +use chrono::prelude::*; +use chrono::Duration; + use rpc::{InstanceLobbyParams, InstanceJoinParams, InstanceReadyParams, InstanceStateParams}; use account::Account; -use player::{Player, Score, player_create, player_get, player_update}; +use player::{Player, player_create, player_get, player_global_update}; use cryp::{Cryp, cryp_get}; use mob::{instance_mobs}; -use game::{Game, Phase, Team, game_get, game_write, game_instance_new, game_instance_join, game_global_get, game_global_set}; +use game::{Game, Phase, game_get, game_write, game_instance_new}; use vbox::{Var}; use rpc::{RpcResult}; use names::{name}; @@ -29,7 +33,7 @@ enum InstancePhase { #[derive(Debug,Clone,Serialize,Deserialize)] struct Round { player_ids: Vec, - game_id: Uuid, + game_id: Option, finished: bool, } @@ -41,8 +45,10 @@ pub struct Instance { rounds: Vec>, open: bool, max_players: usize, + max_rounds: usize, password: Option, pub name: String, + phase_start: DateTime, } impl Instance { @@ -54,8 +60,10 @@ impl Instance { phase: InstancePhase::Lobby, open: true, max_players: 2, + max_rounds: 16, name: String::new(), password: None, + phase_start: Utc::now(), } } @@ -67,11 +75,44 @@ impl Instance { phase: InstancePhase::InProgress, open: false, max_players: 0, + max_rounds: 1, name: "Global Matchmaking".to_string(), password: None, + phase_start: Utc::now(), } } + fn phase_timed_out(&self) -> bool { + Utc::now().signed_duration_since(self.phase_start).num_seconds() > 60 + } + + fn timed_out_players(&self) -> Vec { + self.players + .iter() + .filter(|p| !p.ready) + .filter(|p| self.current_game_id(p.id).is_none()) + .map(|p| p.id) + .collect::>() + } + + pub fn upkeep(mut self) -> (Instance, Vec) { + if self.phase != InstancePhase::InProgress { + return (self, vec![]); + } + + if !self.phase_timed_out() { + return (self, vec![]); + } + + let new_games = self + .timed_out_players() + .iter() + .filter_map(|p| self.player_ready(*p).unwrap()) + .collect::>(); + + (self, new_games) + } + fn set_max_players(mut self, max: usize) -> Result { if max > 16 || max % 2 != 0 { return Err(err_msg("max players must be divisible by 2 and less than 16")); @@ -90,12 +131,21 @@ impl Instance { Ok(self) } + fn set_max_rounds(mut self, rounds: usize) -> Result { + if rounds == 0 { + return Err(err_msg("max rounds must be nonzero")); + } + + self.max_rounds = rounds; + Ok(self) + } + fn add_bots(mut self) -> Instance { self.open = false; self.players = iter::repeat_with(|| { let bot_id = Uuid::new_v4(); let cryps = instance_mobs(bot_id); - let mut p = Player::new(bot_id, self.id, &name(), cryps).set_bot(true); + let mut p = Player::new(bot_id, &name(), cryps).set_bot(true); p.set_ready(true); p }) @@ -114,87 +164,98 @@ impl Instance { Ok(self) } - pub fn player_update(mut self, player: Player, ignore_phase: bool) -> Result { - if !ignore_phase && self.phase != InstancePhase::InProgress { - return Err(format_err!("instance not in vbox phase ({:?})", self.phase)); - } - - let i = self.players - .iter() - .position(|p| p.id == player.id) - .ok_or(err_msg("player_id not found"))?; - - self.players[i] = player; - - Ok(self) - } - - fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Instance, Error> { + 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"))?; - if self.phase != InstancePhase::Lobby && self.players[i].cryps.iter().all(|c| c.skills.len() == 0) { - return Err(err_msg("your cryps have no skills")); - } - let v = !self.players[i].ready; self.players[i].set_ready(v); - if self.phase == InstancePhase::Lobby && self.can_start() { - self.start(); + // start the game even if afk noobs have no skills + if !self.phase_timed_out() && self.players[i].cryps.iter().all(|c| c.skills.len() == 0) { + return Err(err_msg("your cryps have no skills")); } - Ok(self) + // create a game object if both players are ready + // this should only happen once + + let all_ready = self.round_ready_check(player_id); + + if !all_ready { + return Ok(None); + } + + let game = self.create_round_game(player_id); + + { + let round_num = self.rounds.len() - 1; + let current_round = self.rounds[round_num] + .iter_mut() + .find(|g| g.player_ids.contains(&player_id)) + .unwrap(); + + current_round.game_id = Some(game.id); + } + + return Ok(Some(game)); + } - fn player_has_pve_game(&self, player_id: Uuid) -> bool { - let opponent_id = self.current_round(player_id).player_ids - .iter() - .find(|p| **p != player_id) - .expect("unable to find opponent"); - - return self.players - .iter() - .find(|p| p.id == *opponent_id) - .expect("unable to find opponent") - .bot; - } - - fn bot_vs_player_game(&self, player_id: Uuid) -> Result { + fn round_ready_check(&mut self, player_id: Uuid) -> bool { let current_round = self.current_round(player_id); - let bot_id = current_round.player_ids.iter().find(|id| **id != player_id).unwrap(); - - let plr = self.players.clone().into_iter().find(|p| p.id == player_id).unwrap(); - let bot = self.players.clone().into_iter().find(|p| p.id == *bot_id).unwrap(); + self.players + .iter() + .filter(|p| current_round.player_ids.contains(&p.id)) + .all(|p| p.ready) + } + // maybe just embed the games in the instance + // but seems hella inefficient + fn create_round_game(&self, player_id: Uuid) -> Game { + let current_round = self.current_round(player_id); let mut game = Game::new(); - game.id = current_round.game_id; + game - .set_team_num(2) - .set_team_size(3) + .set_player_num(2) + .set_player_cryps(3) .set_instance(self.id); - // add the players - let mut plr_team = Team::new(plr.id); - plr_team.set_cryps(plr.cryps); + // create the initiators player + for player in self.players + .clone() + .into_iter() + .filter(|p| current_round.player_ids.contains(&p.id)) { + game.player_add(player).unwrap(); + } - let mut bot_team = Team::new(bot.id); - bot_team.set_cryps(bot.cryps); - bot_team.set_bot(); - - game - .team_add(plr_team)? - .team_add(bot_team)?; - - game = game.start(); - - Ok(game) + assert!(game.can_start()); + return game.start(); } fn can_start(&self) -> bool { @@ -209,121 +270,47 @@ impl Instance { fn next_round(&mut self) -> &mut Instance { self.phase = InstancePhase::InProgress; + self.phase_start = Utc::now(); + + if self.rounds.len() >= self.max_rounds { + return self.finish(); + } self.players.iter_mut().for_each(|p| { - p.ready = false; + p.set_ready(false); p.vbox.fill(); }); self.generate_rounds(); - self.bot_vbox_phase(); - self.bot_games_phase(); + self.bot_round_actions(); self } - fn all_ready(&self) -> bool { - self.players.iter().all(|p| p.ready) + fn finish(&mut self) -> &mut Instance { + self.phase = InstancePhase::Finished; + self } - fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { - let round_num = self.rounds.len() - 1; - self.rounds[round_num] - .iter_mut() - .find(|r| r.game_id == game.id) - .ok_or(err_msg("could not find matchup in current round"))? - .finished = true; - - let winner = game.winner().ok_or(err_msg("game not finished"))?; - - for team in game.teams.iter() { - let mut player = self.account_player(team.id)?; - match team.id == winner.id { - true => player.add_win(), - false => player.add_loss(), - }; - } - - if self.all_games_finished() { - self.next_round(); - } - - Ok(self) - } - - fn all_games_finished(&self) -> bool { - match self.rounds.last() { - Some(r) => r.iter().all(|g| g.finished), - None => true, - } - } - - fn bot_vbox_phase(&mut self) -> &mut Instance { + fn bot_round_actions(&mut self) -> &mut Instance { for bot in self.players.iter_mut().filter(|p| p.bot) { bot.vbox.fill(); bot.autobuy(); - bot.set_ready(true); } - self - } + let games = self.players + .clone() + .iter() + .filter(|b| b.bot) + .filter_map(|b| self.player_ready(b.id).unwrap()) + .collect::>(); - fn bot_games_phase(&mut self) -> &mut Instance { - if self.phase != InstancePhase::InProgress { - panic!("instance not in progress phase"); - } - - let r = self.rounds.len() - 1; - // println!("round num {:?}", r); - // println!("{:?}", self.rounds[r]); - for mut round in self.rounds[r].iter_mut() { - if self.players - .iter() - .filter(|p| round.player_ids.contains(&p.id) && p.bot && p.ready) - .count() == 2 { - // println!("should play a game between {:?}", round.player_ids); - let a = self.players.clone().into_iter().find(|p| p.id == round.player_ids[0]).unwrap(); - let b = self.players.clone().into_iter().find(|p| p.id == round.player_ids[1]).unwrap(); - - // println!("{:?} vs {:?}", a.name, b.name); - - let mut game = Game::new(); - game - .set_team_num(2) - .set_team_size(3); - - // add the players - let mut a_team = Team::new(a.id); - a_team.set_cryps(a.cryps); - a_team.set_bot(); - - let mut b_team = Team::new(b.id); - b_team.set_cryps(b.cryps); - b_team.set_bot(); - - game - .team_add(a_team).unwrap() - .team_add(b_team).unwrap(); - - game = game.start(); - - assert!(game.finished()); - let winner = match game.winner() { - Some(w) => w, - None => panic!("game has no winner {:?}", game), - }; - - round.finished = true; - - for team in game.teams.iter() { - let mut player = self.players.iter_mut().find(|p| p.id == team.id).unwrap(); - match team.id == winner.id { - true => player.add_win(), - false => player.add_loss(), - }; - } - - } + for game in games { + if game.finished() { + self.game_finished(&game).unwrap(); + } else { + println!("{:?} unfishededes", game); + } } self @@ -350,7 +337,7 @@ impl Instance { .enumerate() .map(|(i, id)| Round { player_ids: vec![*id, matched_players[np - (i + 1)]], - game_id: Uuid::new_v4(), + game_id: None, finished: false, }) .collect::>(); @@ -371,32 +358,60 @@ impl Instance { current_round } - fn current_game(&mut self, player_id: Uuid) -> Option { + fn current_game_id(&self, player_id: Uuid) -> Option { if self.phase == InstancePhase::Lobby { return None; } let current_round = self.current_round(player_id); - let can_start = self.players - .iter() - .filter(|p| current_round.player_ids.contains(&p.id)) - .all(|p| p.ready); - - match can_start { - true => Some(current_round.game_id), - false => None, + if current_round.finished || current_round.game_id.is_none() { + return None; } + + return current_round.game_id; } - fn scores(&self) -> Vec<(String, Score)> { - let mut scores = self.players.iter() - .map(|p| (p.name.clone(), p.score)) - .collect::>(); - scores.sort_unstable_by_key(|s| s.1.wins); - scores.reverse(); + fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { + let round_num = self.rounds.len() - 1; + self.rounds[round_num] + .iter_mut() + .filter(|r| r.game_id.is_some()) + .find(|r| r.game_id.unwrap() == game.id) + .ok_or(err_msg("could not find matchup in current round"))? + .finished = true; - scores + // if you don't win, you lose + // ties can happen if both players forfeit + let winner_id = match game.winner() { + Some(w) => w.id, + None => Uuid::nil(), + }; + + for player in game.players.iter() { + let mut player = self.account_player(player.id)?; + match player.id == winner_id { + true => player.add_win(), + false => player.add_loss(), + }; + } + + 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.iter().all(|g| g.finished), + None => true, + } } // PLAYER ACTIONS @@ -407,37 +422,54 @@ impl Instance { .ok_or(err_msg("account not in instance")) } + pub fn vbox_action_allowed(&self, account: Uuid) -> Result<(), Error> { + if self.phase == InstancePhase::Lobby { + return Err(err_msg("game not yet started")); + } + if self.current_game_id(account).is_some() { + return Err(err_msg("you cannot perform vbox actions while in a game")); + } + + Ok(()) + } + pub fn vbox_discard(mut self, account: Uuid) -> Result { + self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_discard()?; Ok(self) } pub fn vbox_accept(mut self, account: Uuid, group: usize, index: usize) -> Result { + self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_accept(group, index)?; Ok(self) } pub fn vbox_combine(mut self, account: Uuid, indices: Vec) -> Result { + self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_combine(indices)?; Ok(self) } pub fn vbox_reclaim(mut self, account: Uuid, index: usize) -> Result { + self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_reclaim(index)?; Ok(self) } pub fn vbox_apply(mut self, account: Uuid, index: usize, cryp_id: Uuid) -> Result { + self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_apply(index, cryp_id)?; Ok(self) } pub fn vbox_unequip(mut self, account: Uuid, target: Var, cryp_id: Uuid) -> Result { + self.vbox_action_allowed(account)?; self.account_player(account)? .vbox_unequip(target, cryp_id)?; Ok(self) @@ -485,7 +517,8 @@ pub fn instance_get(tx: &mut Transaction, instance_id: Uuid) -> Result Result { return Ok(instance); } +pub fn instances_need_upkeep(tx: &mut Transaction) -> Result, Error> { + let query = " + SELECT data, id + FROM instances + WHERE id != '00000000-0000-0000-0000-000000000000'; + "; + + 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_new(params: InstanceLobbyParams, tx: &mut Transaction, account: &Account) -> Result { let mut instance = match params.players { 1 => Instance::new() @@ -568,84 +628,56 @@ pub fn instance_join(params: InstanceJoinParams, tx: &mut Transaction, account: .collect::, Error>>()?; if cryps.len() != 3 { - return Err(format_err!("incorrect team size. ({:})", 3)); + return Err(format_err!("incorrect player size. ({:})", 3)); } - let player = player_create(tx, Player::new(account.id, instance.id, &account.name, cryps), account)?; + let player = player_create(tx, Player::new(account.id, &account.name, cryps), instance.id, account)?; instance.add_player(player)?; instance_update(tx, instance) } -pub fn instance_ready_global(tx: &mut Transaction, _account: &Account, player: Player) -> Result { - // get the game - let game = match game_global_get(tx) { - Ok(g) => { - println!("received global game {:?}", g.id); - // if there is one try to join - match game_instance_join(tx, player.clone(), g.id) { - Ok(g) => g, - // if fails make a new one - Err(_e) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?, - } - }, - // if not found make a new one - Err(_) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?, - }; +// pub fn instance_ready_global(tx: &mut Transaction, _account: &Account, player: Player) -> Result { +// // get the game +// let game = match game_global_get(tx) { +// Ok(g) => { +// println!("received global game {:?}", g.id); +// // if there is one try to join +// match game_instance_join(tx, player.clone(), g.id) { +// Ok(g) => g, +// // if fails make a new one +// Err(_e) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?, +// } +// }, +// // if not found make a new one +// Err(_) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?, +// }; - // set the current game - game_global_set(tx, &game)?; - Ok(game) -} - -pub fn instance_scores(params: InstanceReadyParams, tx: &mut Transaction, _account: &Account) -> Result, Error> { - let scores = instance_get(tx, params.instance_id)?.scores(); - Ok(scores) -} +// // set the current game +// game_global_set(tx, &game)?; +// Ok(game) +// } pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account: &Account) -> Result { let mut instance = instance_get(tx, params.instance_id)?; let player_id = instance.account_player(account.id)?.id; - instance.player_ready(player_id)?; - if let Some(game_id) = instance.current_game(player_id) { - match instance.player_has_pve_game(player_id) { - true => match game_get(tx, game_id) { - Ok(g) => g, - Err(_) => { - let game = instance.bot_vs_player_game(player_id)?; - game_write(&game, tx)?; - game - }, - }, - false => { - let opponent_id = *instance - .current_round(account.id) - .player_ids - .iter() - .find(|p| **p != account.id) - .expect("could not find opponent"); - - let a = instance.account_player(account.id)?.clone(); - let b = instance.account_player(opponent_id)?.clone(); - let teams = vec![a, b]; - game_instance_new(tx, teams, game_id, instance.id)? - } - }; + if let Some(game) = instance.player_ready(player_id)? { + game_write(tx, &game)?; } instance_update(tx, instance) } pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account: &Account) -> Result { - let mut instance = instance_get(tx, params.instance_id)?; + let instance = instance_get(tx, params.instance_id)?; - if let Some(game_id) = instance.current_game(account.id) { + if let Some(game_id) = instance.current_game_id(account.id) { let game = game_get(tx, game_id)?; + // return the game until it's finished if game.phase != Phase::Finish { - return Ok(RpcResult::GameState(game)) } } @@ -656,14 +688,14 @@ pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account pub fn global_game_finished(tx: &mut Transaction, game: &Game) -> Result<(), Error> { let winner = game.winner().ok_or(err_msg("game not finished"))?; - for team in game.teams.iter() { - let mut player = player_get(tx, team.id, Uuid::nil())?; - match team.id == winner.id { + for player in game.players.iter() { + let mut player = player_get(tx, player.id, Uuid::nil())?; + match player.id == winner.id { true => player.add_win(), false => player.add_loss(), }; player.vbox.fill(); - player_update(tx, player, true)?; + player_global_update(tx, player, true)?; } Ok(()) @@ -687,11 +719,12 @@ mod tests { fn instance_pve_test() { let mut instance = Instance::new() .set_max_players(16).expect("unable to set max players") + .set_max_rounds(2).expect("max rounds failure") .add_bots(); let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); - let player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true); + let player = Player::new(player_account, &"test".to_string(), cryps).set_bot(true); let player_id = player.id; instance.add_player(player).expect("could not add player"); @@ -699,21 +732,9 @@ mod tests { assert_eq!(instance.phase, InstancePhase::Lobby); instance.player_ready(player_id).unwrap(); - assert_eq!(instance.phase, InstancePhase::InProgress); + assert_eq!(instance.phase, InstancePhase::Finished); assert_eq!(instance.rounds[0].len(), 8); - - instance.player_ready(player_id).unwrap(); - - assert!(instance.all_games_finished()); - instance.next_round(); - - instance.player_ready(player_id).unwrap(); - - instance.next_round(); - - instance.player_ready(player_id).unwrap(); - - assert_eq!(instance.rounds.len(), 3); + assert_eq!(instance.rounds.len(), 2); } #[test] @@ -721,7 +742,7 @@ mod tests { let instance = Instance::new(); let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); - let _player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true); + let _player = Player::new(player_account, &"test".to_string(), cryps).set_bot(true); } #[test] @@ -734,7 +755,7 @@ mod tests { let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); - let player = Player::new(player_account, instance.id, &"a".to_string(), cryps); + let player = Player::new(player_account, &"a".to_string(), cryps); let a_id = player.id; instance.add_player(player).expect("could not add player"); @@ -742,7 +763,7 @@ mod tests { let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); - let player = Player::new(player_account, instance.id, &"b".to_string(), cryps); + let player = Player::new(player_account, &"b".to_string(), cryps); let b_id = player.id; instance.add_player(player).expect("could not add player"); @@ -755,5 +776,55 @@ mod tests { 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() + .set_max_players(2) + .expect("could not create instance"); + + let player_account = Uuid::new_v4(); + let cryps = instance_mobs(player_account); + let player = Player::new(player_account, &"a".to_string(), cryps); + 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 cryps = instance_mobs(player_account); + let player = Player::new(player_account, &"b".to_string(), cryps); + 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_start = Utc::now().checked_sub_signed(Duration::seconds(61)).unwrap(); + + let (mut instance, new_games) = instance.upkeep(); + + assert_eq!(new_games.len(), 1); + + let game = &new_games[0]; + assert!(game.finished()); + + instance.game_finished(game).unwrap(); + + assert_eq!(instance.rounds.len(), 2); + assert!(instance.players.iter().all(|p| !p.ready)); + + println!("{:#?}", instance); } } diff --git a/server/src/main.rs b/server/src/main.rs index 8fa4a8b1..084c8dc9 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -3,36 +3,32 @@ extern crate uuid; extern crate tungstenite; extern crate env_logger; extern crate bcrypt; +extern crate chrono; extern crate dotenv; -// extern crate petgraph; extern crate postgres; extern crate r2d2; extern crate r2d2_postgres; extern crate serde; extern crate serde_cbor; -#[macro_use] -extern crate serde_derive; - +#[macro_use] extern crate serde_derive; #[macro_use] extern crate failure; -// #[macro_use] extern crate failure_derive; mod cryp; mod game; mod net; mod skill; mod spec; -// mod passives; mod names; mod rpc; mod account; mod instance; mod player; -// mod zone; mod mob; mod util; mod vbox; +mod warden; use dotenv::dotenv; use net::{start}; diff --git a/server/src/mob.rs b/server/src/mob.rs index 95a63ac4..d04109c7 100644 --- a/server/src/mob.rs +++ b/server/src/mob.rs @@ -21,19 +21,19 @@ pub fn generate_mob() -> Cryp { return mob; } -// fn quick_game(team_size: usize) -> Vec { +// fn quick_game(player_size: usize) -> Vec { // iter::repeat_with(|| // generate_mob() // .set_account(Uuid::nil()) // .learn(Skill::Attack)) -// .take(team_size) +// .take(player_size) // .collect::>() // } -pub fn instance_mobs(team_id: Uuid) -> Vec { +pub fn instance_mobs(player_id: Uuid) -> Vec { iter::repeat_with(|| generate_mob() - .set_account(team_id)) + .set_account(player_id)) // .learn(Skill::Attack)) .take(3) .collect::>() @@ -104,14 +104,14 @@ pub fn instance_mobs(team_id: Uuid) -> Vec { // } -// pub fn generate_mob_team(mode: GameMode, cryps: &Vec) -> Team { -// let mut mob_team = Team::new(Uuid::nil()); +// pub fn generate_mob_player(mode: GameMode, cryps: &Vec) -> Player { +// let mut mob_player = Player::new(Uuid::nil()); // let cryp_lvl = cryps.iter().max_by_key(|c| c.lvl).unwrap().lvl; -// let team_size = cryps.len(); +// let player_size = cryps.len(); // let mobs = match mode { -// GameMode::Normal => quick_game(cryp_lvl, team_size), +// GameMode::Normal => quick_game(cryp_lvl, player_size), // GameMode::Zone3v2Attack => zone_3v2_attack(cryp_lvl), // GameMode::Zone2v2Caster => zone_2v2_caster(cryp_lvl), // GameMode::Zone3v3MeleeMiniboss => zone_3v3_melee_miniboss(cryp_lvl), @@ -119,8 +119,8 @@ pub fn instance_mobs(team_id: Uuid) -> Vec { // _ => panic!("{:?} not handled for pve mobs", mode), // }; -// mob_team.set_cryps(mobs); +// mob_player.set_cryps(mobs); -// return mob_team; +// return mob_player; // } diff --git a/server/src/net.rs b/server/src/net.rs index 862ab83d..4346efcf 100644 --- a/server/src/net.rs +++ b/server/src/net.rs @@ -7,7 +7,8 @@ use std::net::{TcpListener, TcpStream}; use serde_cbor::{to_vec}; use std::env; -use std::thread::spawn; +use std::thread::{spawn, sleep}; +use std::time::Duration; use r2d2::{Pool}; use r2d2::{PooledConnection}; @@ -19,6 +20,7 @@ pub type Db = PooledConnection; use rpc::{Rpc}; use util::{startup}; +use warden::{warden}; // struct Server { // client: WebSocket, @@ -69,9 +71,22 @@ pub fn start() { } let server = TcpListener::bind("0.0.0.0:40000").unwrap(); + + let warden_pool = pool.clone(); + spawn(move || { + loop { + let db_connection = warden_pool.get().expect("unable to get db connection"); + if let Err(e) = warden(db_connection) { + println!("{:?}", e); + } + sleep(Duration::new(5, 0)); + } + + }); + for stream in server.incoming() { let db = pool.clone(); - spawn (move || { + spawn(move || { let mut websocket = accept(stream.unwrap()).unwrap(); let rpc = Rpc {}; diff --git a/server/src/player.rs b/server/src/player.rs index c5edeefd..fbad6d60 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -13,6 +13,7 @@ use cryp::{Cryp, Colours, cryp_get}; use vbox::{Vbox, Var, VarEffect}; use rpc::{PlayerCrypsSetParams}; use instance::{Instance, instance_get, instance_update}; +use skill::{Effect}; const DISCARD_COST: u16 = 5; @@ -25,26 +26,26 @@ pub struct Score { #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Player { pub id: Uuid, - pub instance: Uuid, pub name: String, pub vbox: Vbox, pub score: Score, pub cryps: Vec, pub bot: bool, pub ready: bool, + pub warnings: u8, } impl Player { - pub fn new(account: Uuid, instance: Uuid, name: &String, cryps: Vec) -> Player { + pub fn new(account: Uuid, name: &String, cryps: Vec) -> Player { Player { id: account, - instance, name: name.clone(), - vbox: Vbox::new(account, instance), + vbox: Vbox::new(), score: Score { wins: 0, losses: 0 }, cryps, bot: false, ready: false, + warnings: 0, } } @@ -58,16 +59,26 @@ impl Player { self } + pub fn add_warning(&mut self) -> &mut Player { + self.warnings += 1; + self + } + + pub fn forfeit(&mut self) -> &mut Player { + for cryp in self.cryps.iter_mut() { + cryp.force_ko(); + } + self + } + pub fn add_win(&mut self) -> &mut Player { self.score.wins += 1; - self.set_ready(false); self.vbox.balance_add(12); self } pub fn add_loss(&mut self) -> &mut Player { self.score.losses += 1; - self.set_ready(false); self.vbox.balance_add(9); self } @@ -205,8 +216,8 @@ impl Player { } // now the var has been applied - // recalculate the stats of the whole team - let team_colours = self.cryps.iter().fold(Colours::new(), |tc, c| { + // recalculate the stats of the whole player + let player_colours = self.cryps.iter().fold(Colours::new(), |tc, c| { Colours { red: tc.red + c.colours.red, green: tc.green + c.colours.green, @@ -215,7 +226,7 @@ impl Player { }); for cryp in self.cryps.iter_mut() { - cryp.apply_modifiers(&team_colours); + cryp.apply_modifiers(&player_colours); } Ok(self) @@ -241,8 +252,8 @@ impl Player { } // now the var has been applied - // recalculate the stats of the whole team - let team_colours = self.cryps.iter().fold(Colours::new(), |tc, c| { + // recalculate the stats of the whole player + let player_colours = self.cryps.iter().fold(Colours::new(), |tc, c| { Colours { red: tc.red + c.colours.red, green: tc.green + c.colours.green, @@ -251,7 +262,7 @@ impl Player { }); for cryp in self.cryps.iter_mut() { - cryp.apply_modifiers(&team_colours); + cryp.apply_modifiers(&player_colours); } self.vbox.bound.push(target); @@ -259,6 +270,32 @@ impl Player { Ok(self) } + + // GAME METHODS + pub fn skills_required(&self) -> usize { + let required = self.cryps.iter() + .filter(|c| !c.is_ko()) + .filter(|c| c.available_skills().len() > 0) + .collect::>().len(); + // println!("{:} requires {:} skills this turn", self.id, required); + return required; + } + + pub fn taunting(&self) -> Option<&Cryp> { + self.cryps.iter() + .find(|c| c.affected(Effect::Taunt)) + } + + pub fn set_cryps(&mut self, mut cryps: Vec) -> &mut Player { + cryps.sort_unstable_by_key(|c| c.id); + self.cryps = cryps; + self + } + + pub fn cryp_by_id(&mut self, id: Uuid) -> Option<&mut Cryp> { + self.cryps.iter_mut().find(|c| c.id == id) + } + } pub fn player_get(tx: &mut Transaction, account_id: Uuid, instance_id: Uuid) -> Result { @@ -266,7 +303,8 @@ pub fn player_get(tx: &mut Transaction, account_id: Uuid, instance_id: Uuid) -> SELECT * FROM players WHERE account = $1 - AND instance = $2; + AND instance = $2 + FOR UPDATE; "; let result = tx @@ -284,7 +322,7 @@ pub fn player_get(tx: &mut Transaction, account_id: Uuid, instance_id: Uuid) -> return Ok(data); } -pub fn player_create(tx: &mut Transaction, player: Player, account: &Account) -> Result { +pub fn player_create(tx: &mut Transaction, player: Player, instance: Uuid, account: &Account) -> Result { let player_bytes = to_vec(&player)?; let query = " @@ -294,27 +332,19 @@ pub fn player_create(tx: &mut Transaction, player: Player, account: &Account) -> "; let result = tx - .query(query, &[&Uuid::new_v4(), &player.instance, &account.id, &player_bytes])?; + .query(query, &[&Uuid::new_v4(), &instance, &account.id, &player_bytes])?; let _returned = result.iter().next().expect("no row written"); - println!("wrote player {:} joined instance: {:}", account.name, player.instance); + println!("wrote player {:} joined instance: {:}", account.name, instance); return Ok(player); } -pub fn player_update(tx: &mut Transaction, mut player: Player, ignore_phase: bool) -> Result { +pub fn player_global_update(tx: &mut Transaction, mut player: Player, ignore_phase: bool) -> Result { // sort vbox for niceness player.vbox.bound.sort_unstable(); - // update the instance this player is associated with - // if not a global player - if player.instance != Uuid::nil() { - let instance = instance_get(tx, player.instance)? - .player_update(player.clone(), ignore_phase)?; - instance_update(tx, instance)?; - } - let bytes = to_vec(&player)?; let query = " @@ -353,7 +383,7 @@ pub fn player_delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> { pub fn player_mm_cryps_set(params: PlayerCrypsSetParams, tx: &mut Transaction, account: &Account) -> Result { if params.cryp_ids.len() != 3 { - return Err(err_msg("team size is 3")); + return Err(err_msg("player size is 3")); } let cryps = params.cryp_ids @@ -364,11 +394,11 @@ pub fn player_mm_cryps_set(params: PlayerCrypsSetParams, tx: &mut Transaction, a let player = match player_get(tx, account.id, Uuid::nil()) { Ok(mut p) => { p.cryps = cryps; - p.vbox = Vbox::new(account.id, Uuid::nil()); - player_update(tx, p, false)? + p.vbox = Vbox::new(); + player_global_update(tx, p, false)? }, Err(_) => { - player_create(tx, Player::new(account.id, Uuid::nil(), &account.name, cryps), &account)? + player_create(tx, Player::new(account.id, &account.name, cryps), Uuid::nil(), &account)? } }; @@ -385,7 +415,7 @@ mod tests { fn player_bot_vbox_test() { let player_account = Uuid::new_v4(); let cryps = instance_mobs(player_account); - let mut player = Player::new(player_account, Uuid::new_v4(), &"test".to_string(), cryps).set_bot(true); + let mut player = Player::new(player_account, &"test".to_string(), cryps).set_bot(true); player.vbox.fill(); player.autobuy(); diff --git a/server/src/rpc.rs b/server/src/rpc.rs index 357a1cf3..47a8b989 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -16,13 +16,13 @@ use failure::err_msg; use net::Db; use cryp::{Cryp, cryp_spawn}; -use game::{Game, game_state, game_skill}; +use game::{Game, game_state, game_skill, game_ready}; use account::{Account, account_create, account_login, account_from_token, account_cryps, account_instances}; use skill::{Skill}; // use zone::{Zone, zone_create, zone_join, zone_close}; use spec::{Spec}; use player::{Score, player_mm_cryps_set, Player}; -use instance::{Instance, instance_state, instance_new, instance_ready, instance_join, instance_scores}; +use instance::{Instance, instance_state, instance_new, instance_ready, instance_join}; use vbox::{Var, vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip}; pub struct Rpc; @@ -71,11 +71,11 @@ impl Rpc { "game_state" => Rpc::game_state(data, &mut tx, account.unwrap(), client), "game_skill" => Rpc::game_skill(data, &mut tx, account.unwrap(), client), + "game_ready" => Rpc::game_ready(data, &mut tx, account.unwrap(), client), "instance_join" => Rpc::instance_join(data, &mut tx, account.unwrap(), client), "instance_ready" => Rpc::instance_ready(data, &mut tx, account.unwrap(), client), "instance_new" => Rpc::instance_new(data, &mut tx, account.unwrap(), client), - "instance_scores" => Rpc::instance_scores(data, &mut tx, account.unwrap(), client), "instance_state" => Rpc::instance_state(data, &mut tx, account.unwrap(), client), "player_mm_cryps_set" => Rpc::player_mm_cryps_set(data, &mut tx, account.unwrap(), client), @@ -141,6 +141,18 @@ impl Rpc { return Ok(game_response); } + fn game_ready(data: Vec, tx: &mut Transaction, account: Account, _client: &mut WebSocket) -> Result { + let msg = from_slice::(&data).or(Err(err_msg("invalid params")))?; + + let game_response = RpcResponse { + method: "game_state".to_string(), + params: RpcResult::GameState(game_ready(msg.params, tx, &account)?) + }; + + return Ok(game_response); + } + + fn cryp_spawn(data: Vec, tx: &mut Transaction, account: Account, client: &mut WebSocket) -> Result { let msg = from_slice::(&data).or(Err(err_msg("invalid params")))?; @@ -261,17 +273,6 @@ impl Rpc { // return Ok(response); // } - fn instance_scores(data: Vec, tx: &mut Transaction, account: Account, _client: &mut WebSocket) -> Result { - let msg = from_slice::(&data).or(Err(err_msg("invalid params")))?; - - let response = RpcResponse { - method: "instance_scores".to_string(), - params: RpcResult::InstanceScores(instance_scores(msg.params, tx, &account)?) - }; - - return Ok(response); - } - fn instance_state(data: Vec, tx: &mut Transaction, account: Account, _client: &mut WebSocket) -> Result { let msg = from_slice::(&data).or(Err(err_msg("invalid params")))?; match instance_state(msg.params, tx, &account)? { diff --git a/server/src/skill.rs b/server/src/skill.rs index c173d1e0..ca69723a 100644 --- a/server/src/skill.rs +++ b/server/src/skill.rs @@ -125,7 +125,7 @@ pub fn resolve(skill: Skill, source: &mut Cryp, target: &mut Cryp, mut resolutio #[derive(Debug,Clone,PartialEq,Serialize,Deserialize)] pub struct Cast { pub id: Uuid, - pub source_team_id: Uuid, + pub source_player_id: Uuid, pub source_cryp_id: Uuid, pub target_cryp_id: Uuid, pub skill: Skill, @@ -134,11 +134,11 @@ pub struct Cast { } impl Cast { - pub fn new(source_cryp_id: Uuid, source_team_id: Uuid, target_cryp_id: Uuid, skill: Skill) -> Cast { + pub fn new(source_cryp_id: Uuid, source_player_id: Uuid, target_cryp_id: Uuid, skill: Skill) -> Cast { return Cast { id: Uuid::new_v4(), source_cryp_id, - source_team_id, + source_player_id, target_cryp_id, skill, speed: 0, diff --git a/server/src/spec.rs b/server/src/spec.rs index 017abd2d..2f82fd25 100644 --- a/server/src/spec.rs +++ b/server/src/spec.rs @@ -68,94 +68,94 @@ impl Spec { } } - pub fn apply(&self, modified: u64, base: u64, team_colours: &Colours) -> u64 { + pub fn apply(&self, modified: u64, base: u64, player_colours: &Colours) -> u64 { match *self { // Upgrades to Damage Spec Spec::Damage => modified + base.pct(5), Spec::RedDamageI => modified + { let mut pct = 10; - if team_colours.red >= 5 { pct += 10 }; - if team_colours.red >= 10 { pct += 20 }; - if team_colours.red >= 20 { pct += 40 }; + if player_colours.red >= 5 { pct += 10 }; + if player_colours.red >= 10 { pct += 20 }; + if player_colours.red >= 20 { pct += 40 }; base.pct(pct) }, Spec::GreenDamageI => modified + { let mut pct = 10; - if team_colours.green >= 5 { pct += 10 }; - if team_colours.green >= 10 { pct += 20 }; - if team_colours.green >= 20 { pct += 40 }; + if player_colours.green >= 5 { pct += 10 }; + if player_colours.green >= 10 { pct += 20 }; + if player_colours.green >= 20 { pct += 40 }; base.pct(pct) }, Spec::BlueDamageI => modified + { let mut pct = 10; - if team_colours.blue >= 5 { pct += 10 }; - if team_colours.blue >= 10 { pct += 20 }; - if team_colours.blue >= 20 { pct += 40 }; + if player_colours.blue >= 5 { pct += 10 }; + if player_colours.blue >= 10 { pct += 20 }; + if player_colours.blue >= 20 { pct += 40 }; base.pct(pct) }, Spec::GRDI => modified + { let mut pct = 5; - if team_colours.green >= 2 && team_colours.red >= 2 { pct += 10 }; - if team_colours.green >= 5 && team_colours.red >= 5 { pct += 15 }; - if team_colours.green >= 10 && team_colours.red >= 10 { pct += 30 }; + if player_colours.green >= 2 && player_colours.red >= 2 { pct += 10 }; + if player_colours.green >= 5 && player_colours.red >= 5 { pct += 15 }; + if player_colours.green >= 10 && player_colours.red >= 10 { pct += 30 }; base.pct(pct) }, Spec::GBDI => modified + { let mut pct = 5; - if team_colours.green >= 2 && team_colours.blue >= 2 { pct += 10 }; - if team_colours.green >= 5 && team_colours.blue >= 5 { pct += 15 }; - if team_colours.green >= 10 && team_colours.blue >= 10 { pct += 30 }; + if player_colours.green >= 2 && player_colours.blue >= 2 { pct += 10 }; + if player_colours.green >= 5 && player_colours.blue >= 5 { pct += 15 }; + if player_colours.green >= 10 && player_colours.blue >= 10 { pct += 30 }; base.pct(pct) }, Spec::RBDI => modified + { let mut pct = 5; - if team_colours.blue >= 2 && team_colours.red >= 2 { pct += 10 }; - if team_colours.blue >= 5 && team_colours.red >= 5 { pct += 15 }; - if team_colours.blue >= 10 && team_colours.red >= 10 { pct += 30 }; + if player_colours.blue >= 2 && player_colours.red >= 2 { pct += 10 }; + if player_colours.blue >= 5 && player_colours.red >= 5 { pct += 15 }; + if player_colours.blue >= 10 && player_colours.red >= 10 { pct += 30 }; base.pct(pct) }, // Upgrades to speed Spec Spec::Speed => modified + base.pct(5), Spec::RedSpeedI => modified + { let mut pct = 5; - if team_colours.red >= 5 { pct += 5 }; - if team_colours.red >= 10 { pct += 10 }; - if team_colours.red >= 20 { pct += 20 }; + if player_colours.red >= 5 { pct += 5 }; + if player_colours.red >= 10 { pct += 10 }; + if player_colours.red >= 20 { pct += 20 }; base.pct(pct) }, Spec::GreenSpeedI => modified + { let mut pct = 5; - if team_colours.green >= 5 { pct += 5 }; - if team_colours.green >= 10 { pct += 10 }; - if team_colours.green >= 20 { pct += 20 }; + if player_colours.green >= 5 { pct += 5 }; + if player_colours.green >= 10 { pct += 10 }; + if player_colours.green >= 20 { pct += 20 }; base.pct(pct) }, Spec::BlueSpeedI => modified + { let mut pct = 5; - if team_colours.blue >= 5 { pct += 5 }; - if team_colours.blue >= 10 { pct += 10 }; - if team_colours.blue >= 20 { pct += 20 }; + if player_colours.blue >= 5 { pct += 5 }; + if player_colours.blue >= 10 { pct += 10 }; + if player_colours.blue >= 20 { pct += 20 }; base.pct(pct) }, Spec::GRSpeedI => modified + { let mut pct = 5; - if team_colours.green >= 2 && team_colours.red >= 2 { pct += 5 }; - if team_colours.green >= 5 && team_colours.red >= 5 { pct += 10 }; - if team_colours.green >= 10 && team_colours.red >= 10 { pct += 20 }; + if player_colours.green >= 2 && player_colours.red >= 2 { pct += 5 }; + if player_colours.green >= 5 && player_colours.red >= 5 { pct += 10 }; + if player_colours.green >= 10 && player_colours.red >= 10 { pct += 20 }; base.pct(pct) }, Spec::GBSpeedI => modified + { let mut pct = 5; - if team_colours.green >= 2 && team_colours.blue >= 2 { pct += 5 }; - if team_colours.green >= 5 && team_colours.blue >= 5 { pct += 10 }; - if team_colours.green >= 10 && team_colours.blue >= 10 { pct += 20 }; + if player_colours.green >= 2 && player_colours.blue >= 2 { pct += 5 }; + if player_colours.green >= 5 && player_colours.blue >= 5 { pct += 10 }; + if player_colours.green >= 10 && player_colours.blue >= 10 { pct += 20 }; base.pct(pct) }, Spec::RBSpeedI => modified + { let mut pct = 5; - if team_colours.blue >= 2 && team_colours.red >= 2 { pct += 5 }; - if team_colours.blue >= 5 && team_colours.red >= 5 { pct += 10 }; - if team_colours.blue >= 10 && team_colours.red >= 10 { pct += 20 }; + if player_colours.blue >= 2 && player_colours.red >= 2 { pct += 5 }; + if player_colours.blue >= 5 && player_colours.red >= 5 { pct += 10 }; + if player_colours.blue >= 10 && player_colours.red >= 10 { pct += 20 }; base.pct(pct) }, @@ -163,45 +163,45 @@ impl Spec { Spec::Life => modified + base.pct(5), Spec::GreenLifeI => modified + { let mut mult: u64 = 15; - if team_colours.green >= 5 { mult += 25 }; - if team_colours.green >= 10 { mult += 50 }; - if team_colours.green >= 20 { mult += 75 }; - mult * team_colours.green as u64 + if player_colours.green >= 5 { mult += 25 }; + if player_colours.green >= 10 { mult += 50 }; + if player_colours.green >= 20 { mult += 75 }; + mult * player_colours.green as u64 }, Spec::RedLifeI => modified + { let mut mult: u64 = 15; - if team_colours.red >= 5 { mult += 25 }; - if team_colours.red >= 10 { mult += 50 }; - if team_colours.red >= 20 { mult += 75 }; - mult * team_colours.red as u64 + if player_colours.red >= 5 { mult += 25 }; + if player_colours.red >= 10 { mult += 50 }; + if player_colours.red >= 20 { mult += 75 }; + mult * player_colours.red as u64 }, Spec::BlueLifeI => modified + { let mut mult: u64 = 15; - if team_colours.blue >= 5 { mult += 25 }; - if team_colours.blue >= 10 { mult += 50 }; - if team_colours.blue >= 20 { mult += 75 }; - mult * team_colours.blue as u64 + if player_colours.blue >= 5 { mult += 25 }; + if player_colours.blue >= 10 { mult += 50 }; + if player_colours.blue >= 20 { mult += 75 }; + mult * player_colours.blue as u64 }, Spec::GRLI => modified + { let mut mult: u64 = 10; - if team_colours.green >= 2 && team_colours.red >= 2 { mult += 10 }; - if team_colours.green >= 5 && team_colours.red >= 5 { mult += 25 }; - if team_colours.green >= 10 && team_colours.red >= 10 { mult += 50 }; - mult * (team_colours.green + team_colours.red) as u64 + if player_colours.green >= 2 && player_colours.red >= 2 { mult += 10 }; + if player_colours.green >= 5 && player_colours.red >= 5 { mult += 25 }; + if player_colours.green >= 10 && player_colours.red >= 10 { mult += 50 }; + mult * (player_colours.green + player_colours.red) as u64 }, Spec::GBLI => modified + { let mut mult: u64 = 10; - if team_colours.green >= 2 && team_colours.red >= 2 { mult += 10 }; - if team_colours.green >= 5 && team_colours.red >= 5 { mult += 25 }; - if team_colours.green >= 10 && team_colours.red >= 10 { mult += 50 }; - mult * (team_colours.green + team_colours.red) as u64 + if player_colours.green >= 2 && player_colours.red >= 2 { mult += 10 }; + if player_colours.green >= 5 && player_colours.red >= 5 { mult += 25 }; + if player_colours.green >= 10 && player_colours.red >= 10 { mult += 50 }; + mult * (player_colours.green + player_colours.red) as u64 }, Spec::RBLI => modified + { let mut mult: u64 = 10; - if team_colours.blue >= 2 && team_colours.red >= 2 { mult += 10 }; - if team_colours.blue >= 5 && team_colours.red >= 5 { mult += 25 }; - if team_colours.blue >= 10 && team_colours.red >= 10 { mult += 50 }; - mult * (team_colours.blue + team_colours.red) as u64 + if player_colours.blue >= 2 && player_colours.red >= 2 { mult += 10 }; + if player_colours.blue >= 5 && player_colours.red >= 5 { mult += 25 }; + if player_colours.blue >= 10 && player_colours.red >= 10 { mult += 50 }; + mult * (player_colours.blue + player_colours.red) as u64 }, } } diff --git a/server/src/util.rs b/server/src/util.rs index 89a20b4a..f278ae2c 100644 --- a/server/src/util.rs +++ b/server/src/util.rs @@ -2,14 +2,14 @@ use net::Db; // Db Commons use failure::Error; -use game::{game_global_startup}; +// use game::{game_global_startup}; pub fn startup(db: Db) -> Result<(), Error> { let mut tx = db.transaction()?; println!("running startup fns"); - game_global_startup(&mut tx)?; + // game_global_startup(&mut tx)?; match tx.commit() { Ok(_) => { diff --git a/server/src/vbox.rs b/server/src/vbox.rs index 9f752f26..81dc8523 100644 --- a/server/src/vbox.rs +++ b/server/src/vbox.rs @@ -15,7 +15,6 @@ use account::Account; use rpc::{VboxAcceptParams, VboxDiscardParams, VboxCombineParams, VboxApplyParams, VboxReclaimParams, VboxUnequipParams}; use skill::{Skill}; use spec::{Spec}; -use player::{Player}; use instance::{Instance, instance_get, instance_update}; use cryp::{Colours}; @@ -397,16 +396,13 @@ fn get_combos() -> Vec { #[derive(Debug,Clone,Serialize,Deserialize)] pub struct Vbox { - pub id: Uuid, pub bits: u16, pub free: Vec>, pub bound: Vec, - pub instance: Uuid, - pub account: Uuid, } impl Vbox { - pub fn new(account_id: Uuid, instance_id: Uuid) -> Vbox { + pub fn new() -> Vbox { let starting_items = vec![ Var::Attack, Var::Attack, @@ -414,9 +410,6 @@ impl Vbox { ]; Vbox { - id: Uuid::new_v4(), - account: account_id, - instance: instance_id, free: vec![vec![], vec![], vec![]], bound: starting_items, bits: 18, @@ -558,19 +551,19 @@ pub fn vbox_combine(params: VboxCombineParams, tx: &mut Transaction, account: &A } pub fn vbox_reclaim(params: VboxReclaimParams, tx: &mut Transaction, account: &Account) -> Result { - let mut instance = instance_get(tx, params.instance_id)? + let instance = instance_get(tx, params.instance_id)? .vbox_reclaim(account.id, params.index)?; return instance_update(tx, instance); } pub fn vbox_apply(params: VboxApplyParams, tx: &mut Transaction, account: &Account) -> Result { - let mut instance = instance_get(tx, params.instance_id)? + let instance = instance_get(tx, params.instance_id)? .vbox_apply(account.id, params.index, params.cryp_id)?; return instance_update(tx, instance); } pub fn vbox_unequip(params: VboxUnequipParams, tx: &mut Transaction, account: &Account) -> Result { - let mut instance = instance_get(tx, params.instance_id)? + let instance = instance_get(tx, params.instance_id)? .vbox_unequip(account.id, params.target, params.cryp_id)?; return instance_update(tx, instance); } @@ -581,7 +574,7 @@ mod tests { #[test] fn combine_test() { - let mut vbox = Vbox::new(Uuid::new_v4(), Uuid::new_v4()); + let mut vbox = Vbox::new(); vbox.bound = vec![Var::Attack, Var::Green, Var::Green]; vbox.combine(vec![1,2,0]).unwrap(); assert_eq!(vbox.bound[0], Var::Heal); @@ -601,7 +594,7 @@ mod tests { #[test] fn reclaim_test() { - let mut vbox = Vbox::new(Uuid::new_v4(), Uuid::new_v4()); + let mut vbox = Vbox::new(); vbox.bound = vec![Var::Strike]; vbox.reclaim(0).unwrap(); assert_eq!(vbox.bits, 22); diff --git a/server/src/warden.rs b/server/src/warden.rs new file mode 100644 index 00000000..cf92a9ea --- /dev/null +++ b/server/src/warden.rs @@ -0,0 +1,53 @@ + +// Db Commons +use postgres::transaction::Transaction; +use failure::Error; + +use game::{games_need_upkeep, game_update, game_write, game_delete}; +use instance::{instances_need_upkeep, instance_update}; +use net::{Db}; + +fn fetch_games(mut tx: Transaction) -> Result { + let games = games_need_upkeep(&mut tx)?; + + for mut game in games { + let game = game.upkeep(); + match game_update(&mut tx, &game) { + Ok(_) => (), + Err(e) => { + println!("{:?}", e); + game_delete(&mut tx, game.id)?; + } + } + } + + Ok(tx) +} + +fn fetch_instances(mut tx: Transaction) -> Result { + let instances = instances_need_upkeep(&mut tx)?; + + for mut instance in instances { + let (instance, new_games) = instance.upkeep(); + println!("{:?} new games", new_games.len()); + for game in new_games { + game_write(&mut tx, &game)?; + } + instance_update(&mut tx, instance)?; + } + + Ok(tx) +} + +pub fn warden(db: Db) -> Result<(), Error> { + println!("upkeep beginning"); + fetch_games(db.transaction()?)? + .commit()?; + + fetch_instances(db.transaction()?)? + .commit()?; + + println!("upkeep done"); + + Ok(()) +} \ No newline at end of file diff --git a/server/src/zone.rs b/server/src/zone.rs index b70eaab8..84fe4a9c 100644 --- a/server/src/zone.rs +++ b/server/src/zone.rs @@ -187,7 +187,7 @@ pub fn zone_join(params: ZoneJoinParams, tx: &mut Transaction, account: &Account } // persist - game_write(&game, tx)?; + game_write(tx, &game)?; zone_update(&zone, tx)?; return Ok(game);