diff --git a/client/cryps.css b/client/cryps.css index eb59a642..54d77bf4 100644 --- a/client/cryps.css +++ b/client/cryps.css @@ -340,6 +340,11 @@ header { margin: 0; } +.instance-ui-btn[disabled] { + color: #333; + border-color: #333; +} + .vbox-btn:active, .vbox-btn:hover, .vbox-btn:focus { color: black; } diff --git a/client/src/components/game.component.jsx b/client/src/components/game.component.jsx index 008f141a..338f6121 100644 --- a/client/src/components/game.component.jsx +++ b/client/src/components/game.component.jsx @@ -57,6 +57,7 @@ function GamePanel(props) {
diff --git a/client/src/components/info.component.jsx b/client/src/components/info.component.jsx index e47790f3..c6e27fa0 100644 --- a/client/src/components/info.component.jsx +++ b/client/src/components/info.component.jsx @@ -10,12 +10,13 @@ function Info(args) { info, sendUnequip, instance, + player, setInfo, } = args; function infoVar([type, value]) { let red = 0; let blue = 0; let green = 0; - instance.cryps.forEach(cryp => { + player.cryps.forEach(cryp => { red += cryp.colours.red; blue += cryp.colours.blue; green += cryp.colours.green; @@ -152,18 +153,37 @@ function Info(args) { ); } + function scoreBoard() { + const players = instance.players.map((p, i) => + + {p.name} + {p.score.wins} / {p.score.losses} + {p.ready ? 'ready' : ''} + + ); + + return ( + + + {players} + +
+ ); + } + const infoCryp = activeCryp - ? infoCrypElement(instance.cryps.find(c => c.id === activeCryp.id)) + ? infoCrypElement(player.cryps.find(c => c.id === activeCryp.id)) : null; const otherInfo = info.length ? infoVar(info) : null; - const instanceInfoClass = `instance-info ${activeCryp ? '' : 'hidden'}`; + const instanceInfoClass = `instance-info ${info.length ? '' : 'hidden'}`; return (
+ {scoreBoard()} {infoCryp} {otherInfo}
diff --git a/client/src/components/info.container.jsx b/client/src/components/info.container.jsx index b4cd2779..7507c31f 100644 --- a/client/src/components/info.container.jsx +++ b/client/src/components/info.container.jsx @@ -10,10 +10,11 @@ const addState = connect( info, ws, instance, + player, } = state; function sendUnequip(crypId, item) { - return ws.sendVboxUnequip(instance.instance, crypId, item); + return ws.sendVboxUnequip(instance.id, crypId, item); } return { @@ -21,6 +22,7 @@ const addState = connect( info, sendUnequip, instance, + player, }; }, diff --git a/client/src/components/instance.component.jsx b/client/src/components/instance.component.jsx index f04e151d..7ff0d6a3 100644 --- a/client/src/components/instance.component.jsx +++ b/client/src/components/instance.component.jsx @@ -1,120 +1,16 @@ const preact = require('preact'); -// const key = require('keymaster'); -const range = require('lodash/range'); -const mapValues = require('lodash/mapValues'); const VboxContainer = require('./vbox.container'); const InfoContainer = require('./info.container'); -const molecule = require('./molecule'); - -const { SPECS } = require('../utils'); - -function Cryp(props) { - const { - cryp, - sendVboxApply, - setInfo, - activeVar, - setActiveCryp, - } = props; - - const skills = range(0, 3).map(i => { - const skill = cryp.skills[i]; - const s = skill - ? skill.skill - : ( ); - - function skillClick() { - if (!skill) return false; - setInfo('skill', { skill: skill.skill, cryp }); - return setActiveCryp(cryp); - } - - return ; - }); - - // needed for ondrop to fire - function onDragOver(e) { - e.preventDefault(); - return false; - } - - function onDrop(e) { - e.stopPropagation(); - e.preventDefault(); - const item = parseInt(e.dataTransfer.getData('text'), 10); - return sendVboxApply(cryp.id, item); - } - - function onClick(e) { - e.stopPropagation(); - e.preventDefault(); - - if (activeVar !== null) return sendVboxApply(cryp.id, activeVar); - setInfo(null); - return setActiveCryp(cryp); - } - - const specs = cryp.specs.map((s, i) => { - function specClick() { - setActiveCryp(cryp); - setInfo('spec', { spec: s, cryp }); - } - return ( -
- {SPECS[s].svg(`stat-icon ${SPECS[s].colour}`)} -
{SPECS[s].caption}
-
- ); - }); - - const cTotal = cryp.colours.red + cryp.colours.blue + cryp.colours.green; - const colours = mapValues(cryp.colours, c => { - if (cTotal === 0) return 245; - return Math.floor(c / cTotal * 255); - }); - const alpha = cTotal === 0 ? 1 : 0.75; - const thickness = total => { - if (total < 3) return 1; - if (total < 6) return 2; - if (total < 9) return 3; - return 4; - }; - const border = { border: `${thickness(cTotal)}px solid rgba(${colours.red}, ${colours.green}, ${colours.blue}, ${alpha})` }; - - return ( -
-
-
- {molecule()} -
{cryp.name}
-
-
- {skills} -
-
-
- {specs} -
-
- ); -} +const InstanceCrypsContainer = require('./instance.cryps'); function InstanceComponent(args) { const { - account, instance, + player, quit, // clearInfo, sendInstanceReady, - sendVboxApply, - setInfo, activeVar, activeCryp, setActiveVar, @@ -123,12 +19,6 @@ function InstanceComponent(args) { if (!instance) return
...
; - const player = instance.players.find(p => p.account === account.id); - - const cryps = player.cryps.map((c, i) => Cryp({ - cryp: c, sendVboxApply, setInfo, activeVar, setActiveCryp, - })); - function showVbox(e) { setActiveVar(null); setActiveCryp(null); @@ -162,8 +52,6 @@ function InstanceComponent(args) { ? vboxBtn : teamBtn; - const crypListClass = `cryp-list ${infoSelected ? '' : 'hidden'}`; - const menuBtn = ( + ); + + const actionBtn = player + ? readyBtn + : null; return (
@@ -181,16 +80,10 @@ function InstanceComponent(args) {
 
- + {actionBtn}
-
- {cryps} -
+ ); diff --git a/client/src/components/instance.container.jsx b/client/src/components/instance.container.jsx index e06b35af..efa889fd 100644 --- a/client/src/components/instance.container.jsx +++ b/client/src/components/instance.container.jsx @@ -6,7 +6,7 @@ const Instance = require('./instance.component'); const addState = connect( function receiveState(state) { - const { ws, instance, account, activeVar, activeCryp } = state; + const { ws, instance, player, account, activeVar, activeCryp } = state; function sendInstanceReady() { return ws.sendInstanceReady(instance.id); @@ -16,7 +16,7 @@ const addState = connect( return ws.sendVboxApply(instance.id, crypId, i); } - return { instance, account, sendInstanceReady, sendVboxApply, activeVar, activeCryp }; + return { instance, player, account, sendInstanceReady, sendVboxApply, activeVar, activeCryp }; }, function receiveDispatch(dispatch) { diff --git a/client/src/components/instance.cryps.jsx b/client/src/components/instance.cryps.jsx new file mode 100644 index 00000000..9e1611c5 --- /dev/null +++ b/client/src/components/instance.cryps.jsx @@ -0,0 +1,178 @@ +const { connect } = require('preact-redux'); +const preact = require('preact'); +const range = require('lodash/range'); +const mapValues = require('lodash/mapValues'); + +const molecule = require('./molecule'); +const { SPECS } = require('../utils'); +const actions = require('../actions'); +const SkillBtn = require('./skill.btn'); + +const addState = connect( + function receiveState(state) { + const { ws, instance, player, account, activeVar, activeCryp } = state; + + function sendInstanceReady() { + return ws.sendInstanceReady(instance.id); + } + + function sendVboxApply(crypId, i) { + return ws.sendVboxApply(instance.id, crypId, i); + } + + return { instance, player, account, sendInstanceReady, sendVboxApply, activeVar, activeCryp }; + }, + + function receiveDispatch(dispatch) { + function quit() { + dispatch(actions.setInstance(null)); + } + + function setInfo(item, value) { + dispatch(actions.setInfo([item, value])); + } + + function setActiveVar(value) { + dispatch(actions.setActiveVar(value)); + } + + function setActiveCryp(value) { + dispatch(actions.setActiveCryp(value)); + } + + function clearInfo() { + return dispatch(actions.setInfo([])); + } + + return { quit, clearInfo, setInfo, setActiveVar, setActiveCryp }; + } + +); + +function Cryp(props) { + const { + cryp, + sendVboxApply, + setInfo, + activeVar, + setActiveCryp, + } = props; + + const skills = range(0, 3).map(i => { + const skill = cryp.skills[i]; + const s = skill + ? skill.skill + : ( ); + + function skillClick() { + if (!skill) return false; + setInfo('skill', { skill: skill.skill, cryp }); + return setActiveCryp(cryp); + } + + return ; + }); + + // needed for ondrop to fire + function onDragOver(e) { + e.preventDefault(); + return false; + } + + function onDrop(e) { + e.stopPropagation(); + e.preventDefault(); + const item = parseInt(e.dataTransfer.getData('text'), 10); + return sendVboxApply(cryp.id, item); + } + + function onClick(e) { + e.stopPropagation(); + e.preventDefault(); + + if (activeVar !== null) return sendVboxApply(cryp.id, activeVar); + setInfo(null); + return setActiveCryp(cryp); + } + + const specs = cryp.specs.map((s, i) => { + function specClick() { + setActiveCryp(cryp); + setInfo('spec', { spec: s, cryp }); + } + return ( +
+ {SPECS[s].svg(`stat-icon ${SPECS[s].colour}`)} +
{SPECS[s].caption}
+
+ ); + }); + + const cTotal = cryp.colours.red + cryp.colours.blue + cryp.colours.green; + const colours = mapValues(cryp.colours, c => { + if (cTotal === 0) return 245; + return Math.floor(c / cTotal * 255); + }); + const alpha = cTotal === 0 ? 1 : 0.75; + const thickness = total => { + if (total < 3) return 1; + if (total < 6) return 2; + if (total < 9) return 3; + return 4; + }; + const border = { border: `${thickness(cTotal)}px solid rgba(${colours.red}, ${colours.green}, ${colours.blue}, ${alpha})` }; + + return ( +
+
+
+ {molecule()} +
{cryp.name}
+
+
+ {skills} +
+
+
+ {specs} +
+
+ ); +} + +function InstanceCryps(props) { + const { + player, + activeCryp, + activeVar, + + // clearInfo, + setInfo, + setActiveCryp, + + sendVboxApply, + } = props; + + if (!player) return false; + + const infoSelected = activeVar !== null || activeCryp; + const crypListClass = `cryp-list ${infoSelected ? '' : 'hidden'}`; + + const cryps = player.cryps.map((c, i) => Cryp({ + cryp: c, sendVboxApply, setInfo, activeVar, setActiveCryp, + })); + + return ( +
+ {cryps} +
+ ); +} + +module.exports = addState(InstanceCryps); diff --git a/client/src/components/menu.component.jsx b/client/src/components/menu.component.jsx index c83b9d62..6342909a 100644 --- a/client/src/components/menu.component.jsx +++ b/client/src/components/menu.component.jsx @@ -24,6 +24,7 @@ function Menu(args) { sendInstanceState, sendPlayerMmCrypsSet, sendInstanceJoin, + sendInstanceNew, sendCrypSpawn, instances, } = args; @@ -32,14 +33,22 @@ function Menu(args) { if (!instances) return
...
; const instancePanels = instances.map(instance => { - const player = instance.players.find(p => p.account === account.id); - const name = `${instance.name} | ${player.score.wins} : ${player.score.losses}`; + const player = instance.players.find(p => p.id === account.id); + const scoreText = player + ? `| ${player.score.wins} : ${player.score.losses}` + : ''; + const name = `${instance.name} ${scoreText}`; + + function instanceClick() { + if (!player) return sendInstanceJoin(instance); + return sendInstanceState(instance); + } return ( ); @@ -59,8 +68,8 @@ function Menu(args) { ); diff --git a/client/src/components/menu.container.jsx b/client/src/components/menu.container.jsx index 4e4f9c1f..66430e00 100644 --- a/client/src/components/menu.container.jsx +++ b/client/src/components/menu.container.jsx @@ -7,9 +7,16 @@ const addState = connect( function receiveState(state) { const { ws, cryps, selectedCryps, instances, account } = state; - function sendInstanceJoin() { + function sendInstanceJoin(instance) { if (selectedCryps.length) { - return ws.sendInstanceLobby(selectedCryps); + return ws.sendInstanceJoin(instance.id, selectedCryps); + } + return false; + } + + function sendInstanceNew() { + if (selectedCryps.length) { + return ws.sendInstanceNew(selectedCryps); } return false; } @@ -34,6 +41,7 @@ const addState = connect( cryps, selectedCryps, sendInstanceJoin, + sendInstanceNew, sendInstanceState, sendCrypSpawn, sendPlayerMmCrypsSet, diff --git a/client/src/events.jsx b/client/src/events.jsx index 1de7be65..44cca1cd 100644 --- a/client/src/events.jsx +++ b/client/src/events.jsx @@ -93,9 +93,11 @@ function registerEvents(store) { } function setInstance(v) { - const { account } = store.getState(); - const player = v.players.find(p => p.account === account.id); - store.dispatch(actions.setPlayer(player)); + const { account, ws } = store.getState(); + const player = v.players.find(p => p.id === account.id); + if (player) store.dispatch(actions.setPlayer(player)); + + if (!v) ws.clearInstanceStateTimeout(); return store.dispatch(actions.setInstance(v)); } diff --git a/client/src/keyboard.jsx b/client/src/keyboard.jsx index 678351fb..14392540 100644 --- a/client/src/keyboard.jsx +++ b/client/src/keyboard.jsx @@ -7,6 +7,7 @@ function setupKeys(store) { key('esc', () => store.dispatch(actions.setCombiner([null, null, null]))); key('esc', () => store.dispatch(actions.setReclaiming(false))); key('esc', () => store.dispatch(actions.setActiveSkill(null))); + key('esc', () => store.dispatch(actions.setActiveCryp(null))); } module.exports = setupKeys; diff --git a/client/src/socket.jsx b/client/src/socket.jsx index f0ba9108..5bc1645b 100644 --- a/client/src/socket.jsx +++ b/client/src/socket.jsx @@ -144,12 +144,12 @@ function createSocket(events) { send({ method: 'zone_close', params: { zone_id: zoneId } }); } - function sendInstanceJoin(cryps) { - send({ method: 'instance_join', params: { cryp_ids: cryps, pve: true } }); + function sendInstanceJoin(instanceId, cryps) { + send({ method: 'instance_join', params: { instance_id: instanceId, cryp_ids: cryps } }); } - function sendInstanceLobby(cryps) { - send({ method: 'instance_lobby', params: { cryp_ids: cryps, name: 'dota apem', players: 2 } }); + function sendInstanceNew(cryps) { + send({ method: 'instance_new', params: { cryp_ids: cryps, name: 'dota pros onli', players: 2 } }); } function sendInstanceReady(instanceId) { @@ -187,7 +187,7 @@ function createSocket(events) { let gameStateTimeout; function gameState(response) { const [structName, game] = response; - clearInterval(gameStateTimeout); + clearTimeout(gameStateTimeout); gameStateTimeout = setTimeout(() => sendGameState(game.id), 1000); events.setGame(game); } @@ -205,11 +205,19 @@ function createSocket(events) { events.setZone(zone); } + let instanceStateTimeout; function instanceState(response) { const [structName, i] = response; + clearTimeout(instanceStateTimeout); + instanceStateTimeout = setTimeout(() => sendInstanceState(i.id), 1000); events.setInstance(i); } + function clearInstanceStateTimeout() { + clearTimeout(instanceStateTimeout); + } + + function instanceScores(response) { const [structName, scores] = response; events.setScores(scores); @@ -334,7 +342,7 @@ function createSocket(events) { sendZoneClose, sendInstanceJoin, sendInstanceReady, - sendInstanceLobby, + sendInstanceNew, sendInstanceScores, sendPlayerMmCrypsSet, sendInstanceState, diff --git a/server/src/game.rs b/server/src/game.rs index fd603bde..e64e6e80 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -860,7 +860,7 @@ pub fn game_update(game: &Game, tx: &mut Transaction) -> Result<(), Error> { // Ok(game) // } -pub fn game_instance_new(tx: &mut Transaction, player: Player, game_id: Uuid) -> Result { +pub fn game_instance_new(tx: &mut Transaction, players: Vec, game_id: Uuid, instance_id: Uuid) -> Result { // create the game let mut game = Game::new(); game.id = game_id; @@ -868,14 +868,19 @@ pub fn game_instance_new(tx: &mut Transaction, player: Player, game_id: Uuid) -> game .set_team_num(2) .set_team_size(3) - .set_instance(player.instance) + .set_instance(instance_id) .set_mode(GameMode::Pvp); // create the initiators team - let mut team = Team::new(player.account); - team.set_cryps(player.cryps); + for player in players { + let mut team = Team::new(player.id); + team.set_cryps(player.cryps); + game.team_add(team)?; + } - game.team_add(team)?; + if game.can_start() { + game = game.start(); + } // persist game_write(&game, tx)?; @@ -886,7 +891,7 @@ pub fn game_instance_new(tx: &mut Transaction, player: Player, game_id: Uuid) -> 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.account); + let mut team = Team::new(player.id); team.set_cryps(player.cryps); game.team_add(team)?; diff --git a/server/src/instance.rs b/server/src/instance.rs index 136cbd85..d4c1e13b 100644 --- a/server/src/instance.rs +++ b/server/src/instance.rs @@ -14,14 +14,14 @@ use account::Account; use player::{Player, Score, player_create, player_get, player_update}; use cryp::{Cryp, cryp_get}; use mob::{instance_mobs}; -use game::{Game, Team, game_get, game_write, game_instance_new, game_instance_join, game_global_get, game_global_set}; +use game::{Game, Phase, Team, game_get, game_write, game_instance_new, game_instance_join, game_global_get, game_global_set}; use vbox::{Var}; +use rpc::{RpcResult}; #[derive(Debug,Clone,Copy,PartialEq,Serialize,Deserialize)] enum InstancePhase { - Open, - Vbox, - Games, + Lobby, + InProgress, Finished, } @@ -50,7 +50,7 @@ impl Instance { id: Uuid::new_v4(), players: vec![], rounds: vec![], - phase: InstancePhase::Open, + phase: InstancePhase::Lobby, open: true, max_players: 2, name: String::new(), @@ -63,7 +63,7 @@ impl Instance { id: Uuid::nil(), players: vec![player], rounds: vec![], - phase: InstancePhase::Vbox, + phase: InstancePhase::InProgress, open: false, max_players: 0, name: "Global Matchmaking".to_string(), @@ -103,13 +103,18 @@ impl Instance { self } - fn add_player(&mut self, player: Player) -> &mut Instance { + fn add_player(&mut self, player: Player) -> Result<&mut Instance, Error> { + match self.players.iter().find(|p| p.id == player.id) { + Some(p) => return Err(err_msg("already joined")), + None => (), + }; + self.players.push(player); - self + Ok(self) } pub fn player_update(mut self, player: Player, ignore_phase: bool) -> Result { - if !ignore_phase && self.phase != InstancePhase::Vbox { + if !ignore_phase && self.phase != InstancePhase::InProgress { return Err(format_err!("instance not in vbox phase ({:?})", self.phase)); } @@ -124,7 +129,7 @@ impl Instance { } fn player_ready(&mut self, player_id: Uuid) -> Result<&mut Instance, Error> { - if ![InstancePhase::Vbox, InstancePhase::Open].contains(&self.phase) { + if ![InstancePhase::InProgress, InstancePhase::Lobby].contains(&self.phase) { return Err(err_msg("instance not in start or vbox phase")); } @@ -135,27 +140,17 @@ impl Instance { self.players[i].set_ready(true); - match self.phase { - InstancePhase::Open => { - if self.can_start() { - self.start(); - } - }, - InstancePhase::Vbox => { - if self.all_ready() { - self.games_phase_start(); - } - }, - _ => panic!("unhandled ready phase"), + if self.phase == InstancePhase::Lobby && self.can_start() { + self.start(); } Ok(self) } - fn player_has_pve_game(&self, player: &Player) -> bool { - let opponent_id = self.current_round(&player).player_ids + 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) + .find(|p| **p != player_id) .expect("unable to find opponent"); return self.players @@ -165,11 +160,11 @@ impl Instance { .bot; } - fn bot_vs_player_game(&self, player: &Player) -> Result { - let current_round = self.current_round(player); - let bot_id = current_round.player_ids.iter().find(|id| **id != player.id).unwrap(); + fn bot_vs_player_game(&self, player_id: Uuid) -> Result { + 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 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(); let mut game = Game::new(); @@ -180,10 +175,10 @@ impl Instance { .set_instance(self.id); // add the players - let mut plr_team = Team::new(plr.account); + let mut plr_team = Team::new(plr.id); plr_team.set_cryps(plr.cryps); - let mut bot_team = Team::new(bot.account); + let mut bot_team = Team::new(bot.id); bot_team.set_cryps(bot.cryps); bot_team.set_bot(); @@ -203,18 +198,20 @@ impl Instance { fn start(&mut self) -> &mut Instance { // self.players.sort_unstable_by_key(|p| p.id); self.open = false; - self.vbox_phase_start() + self.next_round() } - fn vbox_phase_start(&mut self) -> &mut Instance { - self.phase = InstancePhase::Vbox; + fn next_round(&mut self) -> &mut Instance { + self.phase = InstancePhase::InProgress; self.players.iter_mut().for_each(|p| { p.ready = false; p.vbox.fill(); }); + self.generate_rounds(); self.bot_vbox_phase(); + self.bot_games_phase(); self } @@ -223,21 +220,6 @@ impl Instance { self.players.iter().all(|p| p.ready) } - // requires no input - // just do it - fn games_phase_start(&mut self) -> &mut Instance { - if self.phase != InstancePhase::Vbox { - panic!("instance not in vbox phase"); - } - assert!(self.all_ready()); - - self.phase = InstancePhase::Games; - - self.bot_games_phase(); - - self - } - fn game_finished(&mut self, game: &Game) -> Result<&mut Instance, Error> { let round_num = self.rounds.len() - 1; self.rounds[round_num] @@ -249,7 +231,7 @@ impl Instance { Ok(self) } - fn games_phase_finished(&self) -> bool { + fn all_games_finished(&self) -> bool { match self.rounds.last() { Some(r) => r.iter().all(|g| g.finished), None => true, @@ -267,8 +249,8 @@ impl Instance { } fn bot_games_phase(&mut self) -> &mut Instance { - if self.phase != InstancePhase::Games { - panic!("instance not in games phase"); + if self.phase != InstancePhase::InProgress { + panic!("instance not in progress phase"); } let r = self.rounds.len() - 1; @@ -277,7 +259,7 @@ impl Instance { for mut round in self.rounds[r].iter_mut() { if self.players .iter() - .filter(|p| round.player_ids.contains(&p.id) && p.bot) + .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(); @@ -291,11 +273,11 @@ impl Instance { .set_team_size(3); // add the players - let mut a_team = Team::new(a.account); + 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.account); + let mut b_team = Team::new(b.id); b_team.set_cryps(b.cryps); b_team.set_bot(); @@ -314,7 +296,7 @@ impl Instance { round.finished = true; for team in game.teams.iter() { - let mut player = self.players.iter_mut().find(|p| p.account == team.id).unwrap(); + 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(), @@ -358,17 +340,35 @@ impl Instance { self } - fn current_round(&self, player: &Player) -> &Round { + fn current_round(&self, player_id: Uuid) -> &Round { let round_num = self.rounds.len() - 1; let current_round = self.rounds[round_num] .iter() - .find(|g| g.player_ids.contains(&player.id)) + .find(|g| g.player_ids.contains(&player_id)) .unwrap(); current_round } + fn current_game(&mut 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, + } + } + fn scores(&self) -> Vec<(String, Score)> { let mut scores = self.players.iter() .map(|p| (p.name.clone(), p.score)) @@ -383,7 +383,7 @@ impl Instance { fn account_player(&mut self, account: Uuid) -> Result<&mut Player, Error> { self.players .iter_mut() - .find(|p| p.account == account) + .find(|p| p.id == account) .ok_or(err_msg("account not in instance")) } @@ -522,7 +522,7 @@ pub fn instance_get_open(tx: &mut Transaction) -> Result { return Ok(instance); } -pub fn instance_lobby(params: InstanceLobbyParams, tx: &mut Transaction, account: &Account) -> Result { +pub fn instance_new(params: InstanceLobbyParams, tx: &mut Transaction, account: &Account) -> Result { let mut instance = Instance::new() .set_max_players(params.players)? .set_name(params.name)?; @@ -545,8 +545,9 @@ pub fn instance_join(params: InstanceJoinParams, tx: &mut Transaction, account: return Err(format_err!("incorrect team size. ({:})", 3)); } - let player = Player::new(account.id, instance.id, &account.name, cryps); - instance.add_player(player); + let player = player_create(tx, Player::new(account.id, instance.id, &account.name, cryps), account)?; + + instance.add_player(player)?; instance_update(tx, instance) } @@ -560,11 +561,11 @@ pub fn instance_ready_global(tx: &mut Transaction, _account: &Account, player: P match game_instance_join(tx, player.clone(), g.id) { Ok(g) => g, // if fails make a new one - Err(_e) => game_instance_new(tx, player, Uuid::new_v4())?, + 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, player, Uuid::new_v4())?, + Err(_) => game_instance_new(tx, vec![player], Uuid::new_v4(), Uuid::nil())?, }; // set the current game @@ -582,11 +583,48 @@ pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account 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)? + } + }; + } + instance_update(tx, instance) } -pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account: &Account) -> Result { - instance_get(tx, params.instance_id) +pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account: &Account) -> Result { + let mut instance = instance_get(tx, params.instance_id)?; + + if let Some(game_id) = instance.current_game(account.id) { + let game = game_get(tx, game_id)?; + + if game.phase != Phase::Finish { + + return Ok(RpcResult::GameState(game)) + } + } + + Ok(RpcResult::InstanceState(instance)) } // pub fn instance_ready(params: InstanceReadyParams, tx: &mut Transaction, account: &Account) -> Result { @@ -602,10 +640,10 @@ pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account // // send game state // match instance.player_ready(player.id) { // Ok(_) => (), -// Err(_) => return game_get(tx, instance.current_round(&player).game_id), +// Err(_) => return game_get(tx, instance.current_round(player.id).game_id), // }; -// let game_id = instance.current_round(&player).game_id; +// let game_id = instance.current_round(player.id).game_id; // let game = match instance.player_has_pve_game(&player) { // true => match game_get(tx, game_id) { @@ -678,8 +716,8 @@ pub fn instance_state(params: InstanceStateParams, tx: &mut Transaction, account // // now modify the players and write them all // // each player update will also update the instance in db -// if instance.games_phase_finished() { -// instance.vbox_phase_start(); +// if instance.all_games_finished() { +// instance.next_round(); // let instance = instance_update(tx, instance)?; // for player in instance.players @@ -711,22 +749,22 @@ mod tests { let player = Player::new(player_account, instance.id, &"test".to_string(), cryps).set_bot(true); let player_id = player.id; - instance.add_player(player); + instance.add_player(player).expect("could not add player"); - assert_eq!(instance.phase, InstancePhase::Open); + assert_eq!(instance.phase, InstancePhase::Lobby); instance.player_ready(player_id).unwrap(); - assert_eq!(instance.phase, InstancePhase::Vbox); + assert_eq!(instance.phase, InstancePhase::InProgress); assert_eq!(instance.rounds[0].len(), 8); instance.player_ready(player_id).unwrap(); - assert!(instance.games_phase_finished()); - instance.vbox_phase_start(); + assert!(instance.all_games_finished()); + instance.next_round(); instance.player_ready(player_id).unwrap(); - instance.vbox_phase_start(); + instance.next_round(); instance.player_ready(player_id).unwrap(); @@ -754,7 +792,7 @@ mod tests { let player = Player::new(player_account, instance.id, &"a".to_string(), cryps); let a_id = player.id; - instance.add_player(player.clone()); + instance.add_player(player).expect("could not add player"); assert!(!instance.can_start()); let player_account = Uuid::new_v4(); @@ -762,14 +800,14 @@ mod tests { let player = Player::new(player_account, instance.id, &"b".to_string(), cryps); let b_id = player.id; - instance.add_player(player); + instance.add_player(player).expect("could not add player"); - assert_eq!(instance.phase, InstancePhase::Open); + assert_eq!(instance.phase, InstancePhase::Lobby); instance.player_ready(a_id).expect("a ready"); assert!(!instance.can_start()); instance.player_ready(b_id).expect("b ready"); - assert_eq!(instance.phase, InstancePhase::Vbox); + assert_eq!(instance.phase, InstancePhase::InProgress); assert!(!instance.can_start()); } diff --git a/server/src/player.rs b/server/src/player.rs index 2016fa6b..1e8fc88c 100644 --- a/server/src/player.rs +++ b/server/src/player.rs @@ -26,7 +26,6 @@ pub struct Score { pub struct Player { pub id: Uuid, pub instance: Uuid, - pub account: Uuid, pub name: String, pub vbox: Vbox, pub score: Score, @@ -38,8 +37,7 @@ pub struct Player { impl Player { pub fn new(account: Uuid, instance: Uuid, name: &String, cryps: Vec) -> Player { Player { - id: Uuid::new_v4(), - account, + id: account, instance, name: name.clone(), vbox: Vbox::new(account, instance), @@ -296,7 +294,7 @@ pub fn player_create(tx: &mut Transaction, player: Player, account: &Account) -> "; let result = tx - .query(query, &[&player.id, &player.instance, &account.id, &player_bytes])?; + .query(query, &[&Uuid::new_v4(), &player.instance, &account.id, &player_bytes])?; let _returned = result.iter().next().expect("no row written"); diff --git a/server/src/rpc.rs b/server/src/rpc.rs index e3fc3ffb..357a1cf3 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -22,7 +22,7 @@ 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_lobby, instance_ready, instance_join, instance_scores}; +use instance::{Instance, instance_state, instance_new, instance_ready, instance_join, instance_scores}; use vbox::{Var, vbox_accept, vbox_apply, vbox_discard, vbox_combine, vbox_reclaim, vbox_unequip}; pub struct Rpc; @@ -74,7 +74,7 @@ impl Rpc { "instance_join" => Rpc::instance_join(data, &mut tx, account.unwrap(), client), "instance_ready" => Rpc::instance_ready(data, &mut tx, account.unwrap(), client), - "instance_lobby" => Rpc::instance_lobby(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), @@ -217,12 +217,12 @@ impl Rpc { }) } - fn instance_lobby(data: Vec, tx: &mut Transaction, account: Account, _client: &mut WebSocket) -> Result { + fn instance_new(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_state".to_string(), - params: RpcResult::InstanceState(instance_lobby(msg.params, tx, &account)?) + params: RpcResult::InstanceState(instance_new(msg.params, tx, &account)?) }; return Ok(response); @@ -274,13 +274,17 @@ impl Rpc { 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")))?; - - let response = RpcResponse { - method: "instance_state".to_string(), - params: RpcResult::InstanceState(instance_state(msg.params, tx, &account)?) - }; - - return Ok(response); + match instance_state(msg.params, tx, &account)? { + RpcResult::GameState(p) => Ok(RpcResponse { + method: "game_state".to_string(), + params: RpcResult::GameState(p), + }), + RpcResult::InstanceState(p) => Ok(RpcResponse { + method: "instance_state".to_string(), + params: RpcResult::InstanceState(p), + }), + _ => Err(err_msg("unhandled instance state")) + } } fn player_mm_cryps_set(data: Vec, tx: &mut Transaction, account: Account, _client: &mut WebSocket) -> Result {