add player role distribution through live session for ST

This commit is contained in:
Steffen 2020-12-15 22:31:50 +01:00
parent 53d608288d
commit f7eef3f8ea
6 changed files with 124 additions and 41 deletions

View File

@ -30,26 +30,25 @@ const channels = {};
// a new client connects // a new client connects
wss.on("connection", function connection(ws, req) { wss.on("connection", function connection(ws, req) {
ws.channel = req.url // url pattern: clocktower.online/<channel>/<playerId|host>
.split("/") const url = req.url.toLocaleLowerCase().split("/");
.pop() ws.playerId = url.pop();
.toLocaleLowerCase(); ws.channel = url.pop();
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 // check for another host on this channel
if ( if (
ws.playerId === "host" &&
channels[ws.channel] && channels[ws.channel] &&
channels[ws.channel].some( channels[ws.channel].some(
client => client =>
client !== ws && client.readyState === WebSocket.OPEN && client.isHost client !== ws &&
client.readyState === WebSocket.OPEN &&
client.playerId === "host"
) )
) { ) {
console.log(ws.channel, "duplicate host"); console.log(ws.channel, "duplicate host");
ws.close(1000, `The channel "${ws.channel}" already has a host`); ws.close(1000, `The channel "${ws.channel}" already has a host`);
return; return;
} }
}
ws.isAlive = true; ws.isAlive = true;
ws.pingStart = new Date().getTime(); ws.pingStart = new Date().getTime();
ws.counter = 0; ws.counter = 0;
@ -81,20 +80,44 @@ wss.on("connection", function connection(ws, req) {
); );
return; return;
} }
const isPing = data.match(/^\["ping/i); const messageType = data
if (!isPing) { .toLocaleLowerCase()
console.log(new Date(), wss.clients.size, ws.channel, data); .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);
} }
// 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) { channels[ws.channel].forEach(function each(client) {
if (client !== ws && client.readyState === WebSocket.OPEN) { if (client !== ws && client.readyState === WebSocket.OPEN) {
// inject latency between both clients if ping message // inject latency between both clients if ping message
if (isPing && client.latency && ws.latency) { if (messageType === '"ping"' && client.latency && ws.latency) {
client.send(data.replace(/latency/, client.latency + ws.latency)); client.send(data.replace(/latency/, client.latency + ws.latency));
} else { } else {
client.send(data); client.send(data);
} }
} }
}); });
}
}); });
}); });

View File

@ -108,6 +108,10 @@
Copy player link Copy player link
<em><font-awesome-icon icon="copy"/></em> <em><font-awesome-icon icon="copy"/></em>
</li> </li>
<li v-if="!session.isSpectator" @click="distributeRoles">
Distribute Characters
<em><font-awesome-icon icon="theater-masks"/></em>
</li>
<li <li
v-if="session.voteHistory.length" v-if="session.voteHistory.length"
@click="toggleModal('voteHistory')" @click="toggleModal('voteHistory')"
@ -258,6 +262,17 @@ export default {
} }
}); });
}, },
distributeRoles() {
if (this.session.isSpectator) return;
const popup =
"Do you want to distribute assigned characters to all SEATED players?";
if (confirm(popup)) {
this.$store.commit("session/distributeRoles", true);
setTimeout((() => {
this.$store.commit("session/distributeRoles", false);
}).bind(this), 2000);
}
},
joinSession() { joinSession() {
const sessionId = prompt( const sessionId = prompt(
"Enter the channel number / name of the session you want to join" "Enter the channel number / name of the session you want to join"

View File

@ -86,6 +86,7 @@
icon="chair" icon="chair"
v-if="player.id && session.sessionId" v-if="player.id && session.sessionId"
class="seat" class="seat"
:class="{ highlight: session.isRolesDistributed }"
/> />
<!-- Ghost vote icon --> <!-- Ghost vote icon -->
@ -136,7 +137,7 @@
v-if="player.id && session.sessionId" v-if="player.id && session.sessionId"
> >
<font-awesome-icon icon="chair" /> <font-awesome-icon icon="chair" />
Vacate seat Empty seat
</li> </li>
</template> </template>
<li <li
@ -613,6 +614,20 @@ li.move:not(.from) .player .overlay svg.move {
filter: drop-shadow(0 0 3px black); filter: drop-shadow(0 0 3px black);
cursor: default; cursor: default;
z-index: 2; z-index: 2;
&.highlight {
animation-iteration-count: 1;
animation: redToWhite 1s normal forwards;
}
}
// highlight animation
@keyframes redToWhite {
from {
color: $demon;
}
to {
color: white;
}
} }
.player.you .seat { .player.you .seat {

View File

@ -309,7 +309,9 @@ export default {
.life, .life,
.token, .token,
.shroud, .shroud,
.night-order { .night-order,
.seat {
animation-delay: ($i - 1) * 50ms;
transition-delay: ($i - 1) * 50ms; transition-delay: ($i - 1) * 50ms;
} }

View File

@ -29,7 +29,8 @@ const state = () => ({
lockedVote: 0, lockedVote: 0,
votingSpeed: 3000, votingSpeed: 3000,
isVoteInProgress: false, isVoteInProgress: false,
voteHistory: [] voteHistory: [],
isRolesDistributed: false
}); });
const getters = {}; const getters = {};
@ -46,6 +47,7 @@ const mutations = {
setVotingSpeed: set("votingSpeed"), setVotingSpeed: set("votingSpeed"),
setVoteInProgress: set("isVoteInProgress"), setVoteInProgress: set("isVoteInProgress"),
claimSeat: set("claimedSeat"), claimSeat: set("claimedSeat"),
distributeRoles: set("isRolesDistributed"),
nomination( nomination(
state, state,
{ nomination, votes, votingSpeed, lockedVote, isVoteInProgress } = {} { nomination, votes, votingSpeed, lockedVote, isVoteInProgress } = {}

View File

@ -1,10 +1,8 @@
import rolesJSON from "../roles.json";
class LiveSession { class LiveSession {
constructor(store) { constructor(store) {
//this._wss = "ws://localhost:8081/";
this._wss = "wss://live.clocktower.online:8080/"; this._wss = "wss://live.clocktower.online:8080/";
this._wss = "wss://baumgart.biz:8080/"; //todo: delete this this._wss = "wss://baumgart.biz:8080/"; //todo: delete this
//this._wss = "ws://localhost:8081/";
this._socket = null; this._socket = null;
this._isSpectator = true; this._isSpectator = true;
this._gamestate = []; this._gamestate = [];
@ -28,7 +26,10 @@ class LiveSession {
_open(channel) { _open(channel) {
this.disconnect(); this.disconnect();
this._socket = new WebSocket( 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.addEventListener("message", this._handleMessage.bind(this));
this._socket.onopen = this._onOpen.bind(this); this._socket.onopen = this._onOpen.bind(this);
@ -283,7 +284,7 @@ class LiveSession {
}); });
// roles are special, because of travelers // roles are special, because of travelers
if (roleId && player.role.id !== roleId) { 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", { this._store.commit("players/update", {
player, player,
property: "role", property: "role",
@ -430,8 +431,8 @@ class LiveSession {
value: {} value: {}
}); });
} else { } else {
// load traveler role // load role
const role = rolesJSON.find(r => r.id === value); const role = this._store.state.roles.get(value);
this._store.commit("players/update", { this._store.commit("players/update", {
player, player,
property: "role", property: "role",
@ -550,6 +551,26 @@ class LiveSession {
this._handlePing([true, value]); 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 * A player nomination. ST only
* This also syncs the voting speed to the players. * This also syncs the voting speed to the players.
@ -695,6 +716,11 @@ export default store => {
case "session/claimSeat": case "session/claimSeat":
session.claimSeat(payload); session.claimSeat(payload);
break; break;
case "session/distributeRoles":
if (payload) {
session.distributeRoles();
}
break;
case "session/nomination": case "session/nomination":
session.nomination(payload); session.nomination(payload);
break; break;