mirror of https://github.com/bra1n/townsquare.git
added session pings and connected player count
This commit is contained in:
parent
28a99ae914
commit
06a11d7208
|
@ -1,19 +1,26 @@
|
||||||
<template>
|
<template>
|
||||||
<div id="controls">
|
<div id="controls">
|
||||||
<Screenshot ref="screenshot"></Screenshot>
|
<Screenshot ref="screenshot"></Screenshot>
|
||||||
|
<span class="session">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
@click="leaveSession"
|
@click="leaveSession"
|
||||||
icon="broadcast-tower"
|
icon="broadcast-tower"
|
||||||
v-if="session.sessionId"
|
v-if="session.sessionId"
|
||||||
v-bind:class="{ spectator: session.isSpectator }"
|
:class="{ spectator: session.isSpectator }"
|
||||||
title="You're currently in a live game!"
|
:title="
|
||||||
|
`You're currently in a live game with ${session.playerCount} other players!`
|
||||||
|
"
|
||||||
/>
|
/>
|
||||||
|
{{ session.playerCount }}
|
||||||
|
</span>
|
||||||
|
<span class="camera">
|
||||||
<font-awesome-icon
|
<font-awesome-icon
|
||||||
icon="camera"
|
icon="camera"
|
||||||
@click="takeScreenshot()"
|
@click="takeScreenshot()"
|
||||||
title="Take a screenshot"
|
title="Take a screenshot"
|
||||||
v-bind:class="{ success: grimoire.isScreenshotSuccess }"
|
:class="{ success: grimoire.isScreenshotSuccess }"
|
||||||
/>
|
/>
|
||||||
|
</span>
|
||||||
<div class="menu" v-bind:class="{ open: grimoire.isMenuOpen }">
|
<div class="menu" v-bind:class="{ open: grimoire.isMenuOpen }">
|
||||||
<font-awesome-icon icon="cog" @click="toggleMenu" />
|
<font-awesome-icon icon="cog" @click="toggleMenu" />
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -241,19 +248,21 @@ export default {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
> svg {
|
> span > svg {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 5;
|
z-index: 5;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
margin-left: 10px;
|
margin-left: 10px;
|
||||||
}
|
}
|
||||||
|
|
||||||
> .fa-broadcast-tower {
|
.session {
|
||||||
|
.fa-broadcast-tower {
|
||||||
color: $demon;
|
color: $demon;
|
||||||
&.spectator {
|
&.spectator {
|
||||||
color: $townsfolk;
|
color: $townsfolk;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.menu {
|
.menu {
|
||||||
|
|
|
@ -38,7 +38,8 @@ export default new Vuex.Store({
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
sessionId: "",
|
sessionId: "",
|
||||||
isSpectator: false
|
isSpectator: false,
|
||||||
|
playerCount: 0
|
||||||
},
|
},
|
||||||
modals: {
|
modals: {
|
||||||
edition: false,
|
edition: false,
|
||||||
|
@ -78,6 +79,9 @@ export default new Vuex.Store({
|
||||||
setSpectator({ session }, spectator) {
|
setSpectator({ session }, spectator) {
|
||||||
session.isSpectator = spectator;
|
session.isSpectator = spectator;
|
||||||
},
|
},
|
||||||
|
setPlayerCount({ session }, playerCount) {
|
||||||
|
session.playerCount = playerCount;
|
||||||
|
},
|
||||||
setBluff({ grimoire }, { index, role } = {}) {
|
setBluff({ grimoire }, { index, role } = {}) {
|
||||||
if (index !== undefined) {
|
if (index !== undefined) {
|
||||||
grimoire.bluffs.splice(index, 1, role);
|
grimoire.bluffs.splice(index, 1, role);
|
||||||
|
|
|
@ -1,11 +1,22 @@
|
||||||
class LiveSession {
|
class LiveSession {
|
||||||
constructor(store) {
|
constructor(store) {
|
||||||
this.wss = "wss://connect.websocket.in/v3/";
|
this._wss = "wss://connect.websocket.in/v3/";
|
||||||
this.key = "zXzDomOphNQ94tWXrHfT8E8gkxjUMSXOQt0ypZetKoFsIUiEBegqWNAlExyd";
|
this._key = "zXzDomOphNQ94tWXrHfT8E8gkxjUMSXOQt0ypZetKoFsIUiEBegqWNAlExyd";
|
||||||
this.socket = null;
|
this._socket = null;
|
||||||
this.isSpectator = true;
|
this._isSpectator = true;
|
||||||
this.gamestate = [];
|
this._gamestate = [];
|
||||||
this.store = store;
|
this._store = store;
|
||||||
|
this._pingInterval = 30 * 1000; // 30 seconds between pings
|
||||||
|
this._pingTimer = null;
|
||||||
|
this._players = {}; // map of players connected to a session
|
||||||
|
this._playerId = Math.random()
|
||||||
|
.toString(36)
|
||||||
|
.substr(2);
|
||||||
|
|
||||||
|
// reconnect to previous session
|
||||||
|
if (this._store.state.session.sessionId) {
|
||||||
|
this.connect(this._store.state.session.sessionId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,12 +26,14 @@ class LiveSession {
|
||||||
*/
|
*/
|
||||||
_open(channel) {
|
_open(channel) {
|
||||||
this.disconnect();
|
this.disconnect();
|
||||||
this.socket = new WebSocket(this.wss + channel + "?apiKey=" + this.key);
|
this._socket = new WebSocket(this._wss + channel + "?apiKey=" + this._key);
|
||||||
this.socket.addEventListener("message", this._handleMessage.bind(this));
|
this._socket.addEventListener("message", this._handleMessage.bind(this));
|
||||||
this.socket.onopen = this._onOpen.bind(this);
|
this._socket.onopen = this._onOpen.bind(this);
|
||||||
this.socket.onclose = () => {
|
this._socket.onclose = () => {
|
||||||
this.socket = null;
|
this._socket = null;
|
||||||
this.store.commit("setSessionId", "");
|
this._store.commit("setSessionId", "");
|
||||||
|
clearInterval(this._pingTimer);
|
||||||
|
this._pingTimer = null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -31,8 +44,8 @@ class LiveSession {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_send(command, params) {
|
_send(command, params) {
|
||||||
if (this.socket) {
|
if (this._socket && this._socket.readyState === 1) {
|
||||||
this.socket.send(JSON.stringify([command, params]));
|
this._socket.send(JSON.stringify([command, params]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,11 +54,22 @@ class LiveSession {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_onOpen() {
|
_onOpen() {
|
||||||
if (this.isSpectator) {
|
if (this._isSpectator) {
|
||||||
this._send("req", "gs");
|
this._send("req", "gs");
|
||||||
} else {
|
} else {
|
||||||
this.sendGamestate();
|
this.sendGamestate();
|
||||||
}
|
}
|
||||||
|
this._ping();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a ping message with player ID and ST flag.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_ping() {
|
||||||
|
this._send("ping", [this._isSpectator, this._playerId]);
|
||||||
|
clearTimeout(this._pingTimer);
|
||||||
|
this._pingTimer = setTimeout(this._ping.bind(this), this._pingInterval);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -71,6 +95,13 @@ class LiveSession {
|
||||||
break;
|
break;
|
||||||
case "player":
|
case "player":
|
||||||
this._updatePlayer(params);
|
this._updatePlayer(params);
|
||||||
|
break;
|
||||||
|
case "ping":
|
||||||
|
this._handlePing(params);
|
||||||
|
break;
|
||||||
|
case "bye":
|
||||||
|
this._handleBye(params);
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -79,7 +110,7 @@ class LiveSession {
|
||||||
* @param channel
|
* @param channel
|
||||||
*/
|
*/
|
||||||
connect(channel) {
|
connect(channel) {
|
||||||
this.isSpectator = this.store.state.session.isSpectator;
|
this._isSpectator = this._store.state.session.isSpectator;
|
||||||
this._open(channel);
|
this._open(channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,9 +118,10 @@ class LiveSession {
|
||||||
* Close the current session, if any.
|
* Close the current session, if any.
|
||||||
*/
|
*/
|
||||||
disconnect() {
|
disconnect() {
|
||||||
if (this.socket) {
|
if (this._socket) {
|
||||||
this.socket.close();
|
this._send("bye", this._playerId);
|
||||||
this.socket = null;
|
this._socket.close();
|
||||||
|
this._socket = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,8 +129,8 @@ class LiveSession {
|
||||||
* Publish the current gamestate.
|
* Publish the current gamestate.
|
||||||
*/
|
*/
|
||||||
sendGamestate() {
|
sendGamestate() {
|
||||||
if (this.isSpectator) return;
|
if (this._isSpectator) return;
|
||||||
this.gamestate = this.store.state.players.players.map(player => ({
|
this._gamestate = this._store.state.players.players.map(player => ({
|
||||||
name: player.name,
|
name: player.name,
|
||||||
isDead: player.isDead,
|
isDead: player.isDead,
|
||||||
isVoteless: player.isVoteless,
|
isVoteless: player.isVoteless,
|
||||||
|
@ -113,8 +145,8 @@ class LiveSession {
|
||||||
: {})
|
: {})
|
||||||
}));
|
}));
|
||||||
this._send("gs", {
|
this._send("gs", {
|
||||||
gamestate: this.gamestate,
|
gamestate: this._gamestate,
|
||||||
edition: this.store.state.edition
|
edition: this._store.state.edition
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,16 +157,16 @@ class LiveSession {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_updateGamestate({ gamestate, edition }) {
|
_updateGamestate({ gamestate, edition }) {
|
||||||
this.store.commit("setEdition", edition);
|
this._store.commit("setEdition", edition);
|
||||||
const players = this.store.state.players.players;
|
const players = this._store.state.players.players;
|
||||||
// adjust number of players
|
// adjust number of players
|
||||||
if (players.length < gamestate.length) {
|
if (players.length < gamestate.length) {
|
||||||
for (let x = players.length; x < gamestate.length; x++) {
|
for (let x = players.length; x < gamestate.length; x++) {
|
||||||
this.store.commit("players/add", gamestate[x].name);
|
this._store.commit("players/add", gamestate[x].name);
|
||||||
}
|
}
|
||||||
} else if (players.length > gamestate.length) {
|
} else if (players.length > gamestate.length) {
|
||||||
for (let x = players.length; x > gamestate.length; x--) {
|
for (let x = players.length; x > gamestate.length; x--) {
|
||||||
this.store.commit("players/remove", x - 1);
|
this._store.commit("players/remove", x - 1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// update status for each player
|
// update status for each player
|
||||||
|
@ -142,34 +174,34 @@ class LiveSession {
|
||||||
const player = players[x];
|
const player = players[x];
|
||||||
const { name, isDead, isVoteless, role } = state;
|
const { name, isDead, isVoteless, role } = state;
|
||||||
if (player.name !== name) {
|
if (player.name !== name) {
|
||||||
this.store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "name",
|
property: "name",
|
||||||
value: name
|
value: name
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (player.isDead !== isDead) {
|
if (player.isDead !== isDead) {
|
||||||
this.store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "isDead",
|
property: "isDead",
|
||||||
value: isDead
|
value: isDead
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (player.isVoteless !== isVoteless) {
|
if (player.isVoteless !== isVoteless) {
|
||||||
this.store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "isVoteless",
|
property: "isVoteless",
|
||||||
value: isVoteless
|
value: isVoteless
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (role && player.role.id !== role.id) {
|
if (role && player.role.id !== role.id) {
|
||||||
this.store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value: role
|
value: role
|
||||||
});
|
});
|
||||||
} else if (!role && player.role.team === "traveler") {
|
} else if (!role && player.role.team === "traveler") {
|
||||||
this.store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value: {}
|
value: {}
|
||||||
|
@ -185,12 +217,12 @@ class LiveSession {
|
||||||
* @param value
|
* @param value
|
||||||
*/
|
*/
|
||||||
sendPlayer({ player, property, value }) {
|
sendPlayer({ player, property, value }) {
|
||||||
if (this.isSpectator || property === "reminders") return;
|
if (this._isSpectator || property === "reminders") return;
|
||||||
const index = this.store.state.players.players.indexOf(player);
|
const index = this._store.state.players.players.indexOf(player);
|
||||||
if (property === "role") {
|
if (property === "role") {
|
||||||
if (value.team && value.team === "traveler") {
|
if (value.team && value.team === "traveler") {
|
||||||
// update local gamestate to remember this player as a traveler
|
// update local gamestate to remember this player as a traveler
|
||||||
this.gamestate[index].role = {
|
this._gamestate[index].role = {
|
||||||
id: player.role.id,
|
id: player.role.id,
|
||||||
team: "traveler",
|
team: "traveler",
|
||||||
name: player.role.name
|
name: player.role.name
|
||||||
|
@ -198,10 +230,10 @@ class LiveSession {
|
||||||
this._send("player", {
|
this._send("player", {
|
||||||
index,
|
index,
|
||||||
property,
|
property,
|
||||||
value: this.gamestate[index].role
|
value: this._gamestate[index].role
|
||||||
});
|
});
|
||||||
} else if (this.gamestate[index].role) {
|
} else if (this._gamestate[index].role) {
|
||||||
delete this.gamestate[index].role;
|
delete this._gamestate[index].role;
|
||||||
this._send("player", { index, property, value: {} });
|
this._send("player", { index, property, value: {} });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
@ -217,7 +249,7 @@ class LiveSession {
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_updatePlayer({ index, property, value }) {
|
_updatePlayer({ index, property, value }) {
|
||||||
const player = this.store.state.players.players[index];
|
const player = this._store.state.players.players[index];
|
||||||
if (!player) return;
|
if (!player) return;
|
||||||
// special case where a player stops being a traveler
|
// special case where a player stops being a traveler
|
||||||
if (
|
if (
|
||||||
|
@ -226,16 +258,47 @@ class LiveSession {
|
||||||
player.role.team === "traveler"
|
player.role.team === "traveler"
|
||||||
) {
|
) {
|
||||||
// reset to an unknown role
|
// reset to an unknown role
|
||||||
this.store.commit("players/update", {
|
this._store.commit("players/update", {
|
||||||
player,
|
player,
|
||||||
property: "role",
|
property: "role",
|
||||||
value: {}
|
value: {}
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// just update the player otherwise
|
// just update the player otherwise
|
||||||
this.store.commit("players/update", { player, property, value });
|
this._store.commit("players/update", { player, property, value });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a ping message by another player / storyteller
|
||||||
|
* @param isSpectator
|
||||||
|
* @param playerId
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handlePing([isSpectator, playerId]) {
|
||||||
|
const now = new Date().getTime();
|
||||||
|
this._players[playerId] = now;
|
||||||
|
// remove players that haven't sent a ping in twice the timespan
|
||||||
|
for (let player in this._players) {
|
||||||
|
if (now - this._players[player] > this._pingTimer * 2) {
|
||||||
|
delete this._players[player];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._store.commit("setPlayerCount", Object.keys(this._players).length);
|
||||||
|
if (!this._isSpectator && !isSpectator) {
|
||||||
|
alert("Another storyteller joined the session!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a player leaving the sessions
|
||||||
|
* @param playerId
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
_handleBye(playerId) {
|
||||||
|
delete this._players[playerId];
|
||||||
|
this._store.commit("setPlayerCount", Object.keys(this._players).length);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = store => {
|
module.exports = store => {
|
||||||
|
|
Loading…
Reference in New Issue