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