From f7eef3f8ea9ba31ec550bc9778cf50b249d5326b Mon Sep 17 00:00:00 2001 From: Steffen Date: Tue, 15 Dec 2020 22:31:50 +0100 Subject: [PATCH] add player role distribution through live session for ST --- server/index.js | 85 ++++++++++++++++++++++------------- src/components/Menu.vue | 15 +++++++ src/components/Player.vue | 17 ++++++- src/components/TownSquare.vue | 4 +- src/store/modules/session.js | 4 +- src/store/socket.js | 40 ++++++++++++++--- 6 files changed, 124 insertions(+), 41 deletions(-) diff --git a/server/index.js b/server/index.js index 8c75117..d326942 100644 --- a/server/index.js +++ b/server/index.js @@ -30,25 +30,24 @@ const channels = {}; // a new client connects wss.on("connection", function connection(ws, req) { - ws.channel = req.url - .split("/") - .pop() - .toLocaleLowerCase(); - if (ws.channel.match(/-host$/i)) { - ws.isHost = true; - ws.channel = ws.channel.substr(0, ws.channel.length - 5); - // check for another host on this channel - if ( - channels[ws.channel] && - channels[ws.channel].some( - client => - client !== ws && client.readyState === WebSocket.OPEN && client.isHost - ) - ) { - console.log(ws.channel, "duplicate host"); - ws.close(1000, `The channel "${ws.channel}" already has a host`); - return; - } + // url pattern: clocktower.online// + const url = req.url.toLocaleLowerCase().split("/"); + ws.playerId = url.pop(); + ws.channel = url.pop(); + // check for another host on this channel + if ( + ws.playerId === "host" && + channels[ws.channel] && + channels[ws.channel].some( + client => + client !== ws && + client.readyState === WebSocket.OPEN && + client.playerId === "host" + ) + ) { + console.log(ws.channel, "duplicate host"); + ws.close(1000, `The channel "${ws.channel}" already has a host`); + return; } ws.isAlive = true; ws.pingStart = new Date().getTime(); @@ -81,20 +80,44 @@ wss.on("connection", function connection(ws, req) { ); return; } - const isPing = data.match(/^\["ping/i); - if (!isPing) { - console.log(new Date(), wss.clients.size, ws.channel, data); + const messageType = data + .toLocaleLowerCase() + .substr(1) + .split(",", 1) + .pop(); + // don't log ping messages + if (messageType !== '"ping"') { + console.log(new Date(), wss.clients.size, ws.channel, ws.playerId, data); } - channels[ws.channel].forEach(function each(client) { - if (client !== ws && client.readyState === WebSocket.OPEN) { - // inject latency between both clients if ping message - if (isPing && client.latency && ws.latency) { - client.send(data.replace(/latency/, client.latency + ws.latency)); - } else { - client.send(data); - } + // handle "direct" messages differently + if (messageType === '"direct"') { + try { + const dataToPlayer = JSON.parse(data)[1]; + channels[ws.channel].forEach(function each(client) { + if ( + client !== ws && + client.readyState === WebSocket.OPEN && + dataToPlayer[client.playerId] + ) { + client.send(JSON.stringify(dataToPlayer[client.playerId])); + } + }); + } catch (e) { + console.log("error parsing direct message JSON", e); } - }); + } else { + // all other messages + channels[ws.channel].forEach(function each(client) { + if (client !== ws && client.readyState === WebSocket.OPEN) { + // inject latency between both clients if ping message + if (messageType === '"ping"' && client.latency && ws.latency) { + client.send(data.replace(/latency/, client.latency + ws.latency)); + } else { + client.send(data); + } + } + }); + } }); }); diff --git a/src/components/Menu.vue b/src/components/Menu.vue index 6406be9..474447a 100644 --- a/src/components/Menu.vue +++ b/src/components/Menu.vue @@ -108,6 +108,10 @@ Copy player link +
  • + Distribute Characters + +
  • { + this.$store.commit("session/distributeRoles", false); + }).bind(this), 2000); + } + }, joinSession() { const sessionId = prompt( "Enter the channel number / name of the session you want to join" diff --git a/src/components/Player.vue b/src/components/Player.vue index f19d9ac..af2570b 100644 --- a/src/components/Player.vue +++ b/src/components/Player.vue @@ -86,6 +86,7 @@ icon="chair" v-if="player.id && session.sessionId" class="seat" + :class="{ highlight: session.isRolesDistributed }" /> @@ -136,7 +137,7 @@ v-if="player.id && session.sessionId" > - Vacate seat + Empty seat
  • ({ lockedVote: 0, votingSpeed: 3000, isVoteInProgress: false, - voteHistory: [] + voteHistory: [], + isRolesDistributed: false }); const getters = {}; @@ -46,6 +47,7 @@ const mutations = { setVotingSpeed: set("votingSpeed"), setVoteInProgress: set("isVoteInProgress"), claimSeat: set("claimedSeat"), + distributeRoles: set("isRolesDistributed"), nomination( state, { nomination, votes, votingSpeed, lockedVote, isVoteInProgress } = {} diff --git a/src/store/socket.js b/src/store/socket.js index 194e246..7fbc1d1 100644 --- a/src/store/socket.js +++ b/src/store/socket.js @@ -1,10 +1,8 @@ -import rolesJSON from "../roles.json"; - class LiveSession { constructor(store) { - //this._wss = "ws://localhost:8081/"; this._wss = "wss://live.clocktower.online:8080/"; this._wss = "wss://baumgart.biz:8080/"; //todo: delete this + //this._wss = "ws://localhost:8081/"; this._socket = null; this._isSpectator = true; this._gamestate = []; @@ -28,7 +26,10 @@ class LiveSession { _open(channel) { this.disconnect(); this._socket = new WebSocket( - this._wss + channel + (this._isSpectator ? "" : "-host") + this._wss + + channel + + "/" + + (this._isSpectator ? this._store.state.session.playerId : "host") ); this._socket.addEventListener("message", this._handleMessage.bind(this)); this._socket.onopen = this._onOpen.bind(this); @@ -283,7 +284,7 @@ class LiveSession { }); // roles are special, because of travelers if (roleId && player.role.id !== roleId) { - const role = rolesJSON.find(r => r.id === roleId); + const role = this._store.state.roles.get(roleId); this._store.commit("players/update", { player, property: "role", @@ -430,8 +431,8 @@ class LiveSession { value: {} }); } else { - // load traveler role - const role = rolesJSON.find(r => r.id === value); + // load role + const role = this._store.state.roles.get(value); this._store.commit("players/update", { player, property: "role", @@ -550,6 +551,26 @@ class LiveSession { this._handlePing([true, value]); } + /** + * Distribute player roles to all seated players in a direct message. + * This will be split server side so that each player only receives their own (sub)message. + */ + distributeRoles() { + if (this._isSpectator) return; + const message = {}; + this._store.state.players.players.forEach((player, index) => { + if (player.id && player.role) { + message[player.id] = [ + "player", + { index, property: "role", value: player.role.id } + ]; + } + if (Object.keys(message).length) { + this._send("direct", message); + } + }); + } + /** * A player nomination. ST only * This also syncs the voting speed to the players. @@ -695,6 +716,11 @@ export default store => { case "session/claimSeat": session.claimSeat(payload); break; + case "session/distributeRoles": + if (payload) { + session.distributeRoles(); + } + break; case "session/nomination": session.nomination(payload); break;