From abf5c45aa7fb969fe89010389c232f155b3e980a Mon Sep 17 00:00:00 2001 From: LuckyLaszlo Date: Thu, 8 Dec 2022 14:43:07 +0100 Subject: [PATCH] spectator mode WIP, working so far --- memo.txt | 4 +- src/client/gameLoop.ts | 44 +++++++++--- src/client/global.ts | 30 +++++++- src/client/pong.ts | 15 ++-- src/client/pongSpectator.html | 19 +++++ src/client/pongSpectator.ts | 46 ++++++++++++ src/client/ws.ts | 124 ++++++++++++++++++++++++++++++-- src/server/class/Client.ts | 4 +- src/server/class/GameSession.ts | 78 +++++++++++++------- src/server/constants.ts | 3 +- src/server/wsServer.ts | 40 +++++++---- src/shared_js/class/Event.ts | 20 +++++- src/shared_js/constants.ts | 4 ++ 13 files changed, 364 insertions(+), 67 deletions(-) create mode 100644 src/client/pongSpectator.html create mode 100644 src/client/pongSpectator.ts diff --git a/memo.txt b/memo.txt index 9af85cef..73d12845 100644 --- a/memo.txt +++ b/memo.txt @@ -1,5 +1,5 @@ TODO: - - mode spectateur + - mode spectateur (WIP, semble fonctionner) - certaines utilisations de Math.floor() superflu ? Vérifier les appels. (éventuellement Math.round() ?) - un autre mode de jeu alternatif ? @@ -29,6 +29,8 @@ Done: ----------- BUG: + - Uncaught (in promise) DOMException: The element has no supported sources. + Bug de son. Peut etre du au CORS. Il faut tester de referencer le son avec u chemin relatif. - Si la balle va très vite, elle peut ignorer la collision avec une raquette ou mur. la collision est testée seulement après le mouvement. Pour éviter ce bug il faudrait diviser le mouvement pour faire plusieurs tests de collision successifs. diff --git a/src/client/gameLoop.ts b/src/client/gameLoop.ts index 60e25753..784841c3 100644 --- a/src/client/gameLoop.ts +++ b/src/client/gameLoop.ts @@ -1,8 +1,10 @@ import * as c from "./constants.js"; import * as en from "../shared_js/enums.js" -import { gc, matchOptions, clientInfo } from "./global.js"; +import { gc, matchOptions, clientInfo, clientInfoSpectator} from "./global.js"; import { wallsMovements } from "../shared_js/wallsMovement.js"; +import { RacketClient } from "./class/RectangleClient.js"; +import { VectorInteger } from "../shared_js/class/Vector.js"; let actual_time: number = Date.now(); let last_time: number; @@ -20,7 +22,7 @@ export function gameLoop() // interpolation // console.log(`dir.y: ${clientInfo.opponent.dir.y}, pos.y: ${clientInfo.opponent.pos.y}, opponentNextPos.y: ${clientInfo.opponentNextPos.y}`); if (clientInfo.opponent.dir.y != 0 ) { - opponentInterpolation(delta_time); + racketInterpolation(delta_time, clientInfo.opponent, clientInfo.opponentNextPos); } // client prediction @@ -33,15 +35,37 @@ export function gameLoop() } } -function opponentInterpolation(delta: number) +export function gameLoopSpectator() { - // interpolation - clientInfo.opponent.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]); + delta_time = c.fixedDeltaTime; - if ((clientInfo.opponent.dir.y > 0 && clientInfo.opponent.pos.y > clientInfo.opponentNextPos.y) - || (clientInfo.opponent.dir.y < 0 && clientInfo.opponent.pos.y < clientInfo.opponentNextPos.y)) - { - clientInfo.opponent.dir.y = 0; - clientInfo.opponent.pos.y = clientInfo.opponentNextPos.y; + // interpolation + if (gc.playerLeft.dir.y != 0 ) { + racketInterpolation(delta_time, gc.playerLeft, clientInfoSpectator.playerLeftNextPos); + } + if (gc.playerRight.dir.y != 0 ) { + racketInterpolation(delta_time, gc.playerRight, clientInfoSpectator.playerRightNextPos); + } + + // client prediction + gc.ballsArr.forEach((ball) => { + ball.moveAndBounce(delta_time, [gc.wallTop, gc.wallBottom, gc.playerLeft, gc.playerRight]); + }); + + if (matchOptions & en.MatchOptions.movingWalls) { + wallsMovements(delta_time, gc); + } +} + +function racketInterpolation(delta: number, racket: RacketClient, nextPos: VectorInteger) +{ + // interpolation + racket.moveAndCollide(delta, [gc.wallTop, gc.wallBottom]); + + if ((racket.dir.y > 0 && racket.pos.y > nextPos.y) + || (racket.dir.y < 0 && racket.pos.y < nextPos.y)) + { + racket.dir.y = 0; + racket.pos.y = nextPos.y; } } diff --git a/src/client/global.ts b/src/client/global.ts index 7d0a7126..c789fca6 100644 --- a/src/client/global.ts +++ b/src/client/global.ts @@ -1,3 +1,29 @@ -export {pong, gc, matchOptions} from "./pong.js" -export {socket, clientInfo} from "./ws.js" +import * as en from "../shared_js/enums.js"; +import { GameArea } from "./class/GameArea.js"; +import { GameComponentsClient } from "./class/GameComponentsClient.js"; + +// export {pong, gc, matchOptions} from "./pong.js" +export {socket, clientInfo, clientInfoSpectator} from "./ws.js" + +export let pong: GameArea; +export let gc: GameComponentsClient; +export let matchOptions: en.MatchOptions = en.MatchOptions.noOption; + +export function initPong(value: GameArea) { + pong = value; +} + +export function initGc(value: GameComponentsClient) { + gc = value; +} + +export function initMatchOptions(value: en.MatchOptions) { + matchOptions = value; +} + +export let startFunction: () => void; + +export function initStartFunction(value: () => void) { + startFunction = value; +} diff --git a/src/client/pong.ts b/src/client/pong.ts index a66dc9d5..07363807 100644 --- a/src/client/pong.ts +++ b/src/client/pong.ts @@ -18,9 +18,9 @@ import { initAudio } from "./audio.js"; /* TODO: A way to delay the init of variables, but still use "const" not "let" ? */ -export let pong: GameArea; -export let gc: GameComponentsClient; -export let matchOptions: en.MatchOptions = en.MatchOptions.noOption; +import { pong, gc } from "./global.js" +import { initPong, initGc, initMatchOptions, initStartFunction } from "./global.js" + function init() { @@ -34,22 +34,25 @@ function init() } initAudio(soundMutedFlag); + let matchOptions: en.MatchOptions = en.MatchOptions.noOption; if ( (document.getElementById("multi_balls")).checked ) { matchOptions |= en.MatchOptions.multiBalls; } if ( (document.getElementById("moving_walls")).checked ) { matchOptions |= en.MatchOptions.movingWalls; } + initMatchOptions(matchOptions); document.getElementById("div_game_options").remove(); document.getElementById("div_game_instructions").remove(); - pong = new GameArea(); - gc = new GameComponentsClient(matchOptions, pong.ctx); + initPong(new GameArea()); + initGc(new GameComponentsClient(matchOptions, pong.ctx)); + initStartFunction(start); initWebSocket(matchOptions); } -export function start() +function start() { gc.text1.pos.assign(c.w*0.5, c.h*0.75); countdown(c.matchStartDelay/1000, (count: number) => { diff --git a/src/client/pongSpectator.html b/src/client/pongSpectator.html new file mode 100644 index 00000000..f0357cb6 --- /dev/null +++ b/src/client/pongSpectator.html @@ -0,0 +1,19 @@ + + + + + + + + +
+ +
+
+

Spectator View

+
+ + + + + diff --git a/src/client/pongSpectator.ts b/src/client/pongSpectator.ts new file mode 100644 index 00000000..4efb9a19 --- /dev/null +++ b/src/client/pongSpectator.ts @@ -0,0 +1,46 @@ + +initSpectator(); +function initSpectator() { + // Wip + init(); +} + +import * as c from "./constants.js" +import * as en from "../shared_js/enums.js" +import { GameArea } from "./class/GameArea.js"; +import { GameComponentsClient } from "./class/GameComponentsClient.js"; +import { gameLoopSpectator } from "./gameLoop.js" +import { drawLoop } from "./draw.js"; +import { initWebSocketSpectator } from "./ws.js"; +import { initAudio } from "./audio.js"; + + +/* TODO: A way to delay the init of variables, but still use "const" not "let" ? */ +import { pong, gc } from "./global.js" +import { initPong, initGc, initMatchOptions, initStartFunction } from "./global.js" + +function init() +{ + initAudio(false); + + // WIP matchOptions + let matchOptions: en.MatchOptions = en.MatchOptions.noOption; + initMatchOptions(matchOptions); + + + initPong(new GameArea()); + initGc(new GameComponentsClient(matchOptions, pong.ctx)); + initStartFunction(start); + initWebSocketSpectator(c.gameSessionIdPLACEHOLDER); +} + +function start() +{ + resume(); +} + +function resume() +{ + pong.gameLoopInterval = window.setInterval(gameLoopSpectator, c.gameLoopIntervalMS); + pong.drawLoopInterval = window.setInterval(drawLoop, c.drawLoopIntervalMS); +} diff --git a/src/client/ws.ts b/src/client/ws.ts index ca3a1227..aa901cc0 100644 --- a/src/client/ws.ts +++ b/src/client/ws.ts @@ -1,10 +1,9 @@ import * as c from "./constants.js" -import { gc, matchOptions } from "./global.js" +import { gc, matchOptions, startFunction } from "./global.js" import * as ev from "../shared_js/class/Event.js" import * as en from "../shared_js/enums.js" import * as msg from "./message.js"; -import { start } from "./pong.js"; import { RacketClient } from "./class/RectangleClient.js"; import { repeatInput } from "./handleInput.js"; import { soundRoblox } from "./audio.js" @@ -19,16 +18,24 @@ class ClientInfo { opponentNextPos: VectorInteger; } +class ClientInfoSpectator { + // side: en.PlayerSide; + /* WIP: playerLeftNextPos and playerRightNextPos could be in clientInfo for simplicity */ + playerLeftNextPos: VectorInteger; + playerRightNextPos: VectorInteger; +} + const wsPort = 8042; const wsUrl = "ws://" + document.location.hostname + ":" + wsPort + "/pong"; export let socket: WebSocket; /* TODO: A way to still use "const" not "let" ? */ export const clientInfo = new ClientInfo(); +export const clientInfoSpectator = new ClientInfoSpectator(); // WIP, could refactor this export function initWebSocket(options: en.MatchOptions) { socket = new WebSocket(wsUrl, "json"); socket.addEventListener("open", (event) => { - socket.send(JSON.stringify( new ev.ClientAnnounce(en.ClientRole.player, options, clientInfo.id) )); + socket.send(JSON.stringify( new ev.ClientAnnouncePlayer(options, clientInfo.id) )); }); // socket.addEventListener("message", logListener); // for testing purpose socket.addEventListener("message", preMatchListener); @@ -68,7 +75,7 @@ function preMatchListener(this: WebSocket, event: MessageEvent) case en.EventTypes.matchStart: socket.removeEventListener("message", preMatchListener); socket.addEventListener("message", inGameListener); - start(); + startFunction(); break; case en.EventTypes.matchAbort: socket.removeEventListener("message", preMatchListener); @@ -77,7 +84,7 @@ function preMatchListener(this: WebSocket, event: MessageEvent) } } -function inGameListener(event: MessageEvent) +function inGameListener(this: WebSocket, event: MessageEvent) { const data: ev.ServerEvent = JSON.parse(event.data); switch (data.type) { @@ -186,3 +193,110 @@ function matchEnd(data: ev.EventMatchEnd) } // export let matchEnded = false; // unused + + + +/* Spectator */ + +export function initWebSocketSpectator(gameSessionId: string) +{ + socket = new WebSocket(wsUrl, "json"); + socket.addEventListener("open", (event) => { + socket.send(JSON.stringify( new ev.ClientAnnounceSpectator(gameSessionId) )); + }); + // socket.addEventListener("message", logListener); // for testing purpose + socket.addEventListener("message", preMatchListenerSpectator); + + clientInfoSpectator.playerLeftNextPos = new VectorInteger(gc.playerLeft.pos.x, gc.playerLeft.pos.y); + clientInfoSpectator.playerRightNextPos = new VectorInteger(gc.playerRight.pos.x, gc.playerRight.pos.y); + +} + +export function preMatchListenerSpectator(this: WebSocket, event: MessageEvent) +{ + const data: ev.ServerEvent = JSON.parse(event.data); + if (data.type === en.EventTypes.matchStart) + { + socket.removeEventListener("message", preMatchListenerSpectator); + socket.addEventListener("message", inGameListenerSpectator); + startFunction(); + } +} + +function inGameListenerSpectator(this: WebSocket, event: MessageEvent) +{ + const data: ev.ServerEvent = JSON.parse(event.data); + switch (data.type) { + case en.EventTypes.gameUpdate: + gameUpdateSpectator(data as ev.EventGameUpdate); + break; + case en.EventTypes.scoreUpdate: + scoreUpdateSpectator(data as ev.EventScoreUpdate); + break; + case en.EventTypes.matchEnd: + matchEndSpectator(data as ev.EventMatchEnd); + break; + } +} + +function gameUpdateSpectator(data: ev.EventGameUpdate) +{ + console.log("gameUpdateSpectator"); + + if (matchOptions & en.MatchOptions.movingWalls) { + gc.wallTop.pos.y = data.wallTop.y; + gc.wallBottom.pos.y = data.wallBottom.y; + } + + data.ballsArr.forEach((ball, i) => { + gc.ballsArr[i].pos.assign(ball.x, ball.y); + gc.ballsArr[i].dir.assign(ball.dirX, ball.dirY); + gc.ballsArr[i].speed = ball.speed; + }); + + // interpolation + for (const racket of [gc.playerLeft, gc.playerRight]) + { + let nextPos: VectorInteger; + if (racket === gc.playerLeft) { + nextPos = clientInfoSpectator.playerLeftNextPos; + } + else { + nextPos = clientInfoSpectator.playerRightNextPos; + } + + racket.pos.assign(nextPos.x, nextPos.y); + if (racket === gc.playerLeft) { + nextPos.assign(racket.pos.x, data.playerLeft.y); + } + else { + nextPos.assign(racket.pos.x, data.playerRight.y); + } + + racket.dir = new Vector( + nextPos.x - racket.pos.x, + nextPos.y - racket.pos.y + ); + + if (Math.abs(racket.dir.x) + Math.abs(racket.dir.y) !== 0) { + racket.dir = racket.dir.normalized(); + } + } +} + +function scoreUpdateSpectator(data: ev.EventScoreUpdate) +{ + console.log("scoreUpdateSpectator"); + gc.scoreLeft.value = data.scoreLeft; + gc.scoreRight.value = data.scoreRight; +} + +function matchEndSpectator(data: ev.EventMatchEnd) +{ + console.log("matchEndSpectator"); + // WIP + /* msg.win(); + if (data.forfeit) { + msg.forfeit(clientInfo.side); + } */ +} diff --git a/src/server/class/Client.ts b/src/server/class/Client.ts index df8f996b..c6e4defa 100644 --- a/src/server/class/Client.ts +++ b/src/server/class/Client.ts @@ -10,7 +10,6 @@ export class Client { id: string; // same as "socket.id" isAlive: boolean = true; gameSession: GameSession = null; - matchOptions: en.MatchOptions = 0; constructor(socket: WebSocket, id: string) { this.socket = socket; this.id = id; @@ -18,6 +17,7 @@ export class Client { } export class ClientPlayer extends Client { + matchOptions: en.MatchOptions = 0; inputBuffer: ev.EventInput = new ev.EventInput(); lastInputId: number = 0; racket: Racket; @@ -27,7 +27,7 @@ export class ClientPlayer extends Client { } } -export class ClientSpectator extends Client { // Wip, unused +export class ClientSpectator extends Client { constructor(socket: WebSocket, id: string) { super(socket, id); } diff --git a/src/server/class/GameSession.ts b/src/server/class/GameSession.ts index a37204a6..55879135 100644 --- a/src/server/class/GameSession.ts +++ b/src/server/class/GameSession.ts @@ -2,7 +2,7 @@ import * as en from "../../shared_js/enums.js" import * as ev from "../../shared_js/class/Event.js" import * as c from "../constants.js" -import { ClientPlayer } from "./Client"; +import { ClientPlayer, ClientSpectator } from "./Client"; import { GameComponentsServer } from "./GameComponentsServer.js"; import { clientInputListener } from "../wsServer.js"; import { random } from "../utils.js"; @@ -18,8 +18,10 @@ export class GameSession { id: string; // url ? playersMap: Map = new Map(); unreadyPlayersMap: Map = new Map(); + spectatorsMap: Map = new Map(); gameLoopInterval: NodeJS.Timer | number = 0; - clientsUpdateInterval: NodeJS.Timer | number = 0; + playersUpdateInterval: NodeJS.Timer | number = 0; + spectatorsUpdateInterval: NodeJS.Timer | number = 0; components: GameComponentsServer; matchOptions: en.MatchOptions; matchEnded: boolean = false; @@ -50,7 +52,8 @@ export class GameSession { s.actual_time = Date.now(); s.gameLoopInterval = setInterval(s._gameLoop, c.serverGameLoopIntervalMS, s); - s.clientsUpdateInterval = setInterval(s._clientsUpdate, c.clientsUpdateIntervalMS, s); + s.playersUpdateInterval = setInterval(s._playersUpdate, c.playersUpdateIntervalMS, s); + s.spectatorsUpdateInterval = setInterval(s._spectatorsUpdate, c.spectatorsUpdateIntervalMS, s); } pause(s: GameSession) { s.playersMap.forEach( (client) => { @@ -58,7 +61,8 @@ export class GameSession { }); clearInterval(s.gameLoopInterval); - clearInterval(s.clientsUpdateInterval); + clearInterval(s.playersUpdateInterval); + clearInterval(s.spectatorsUpdateInterval); } instantInputDebug(client: ClientPlayer) { this._handleInput(c.fixedDeltaTime, client); @@ -114,24 +118,46 @@ export class GameSession { if (this.matchEnded) { return; } - - if (ball.pos.x > c.w) { ++gc.scoreLeft; } - else if (ball.pos.x < 0 - ball.width) { ++gc.scoreRight; } - - this.playersMap.forEach( (client) => { - client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight))); - }); + this._scoreUpdate(ball); setTimeout(this._newRound, c.newRoundDelay, this, ball); } } } - private _clientsUpdate(s: GameSession) { - const gc = s.components; - const update = new ev.EventGameUpdate(); - update.playerLeft.y = gc.playerLeft.pos.y; - update.playerRight.y = gc.playerRight.pos.y; + private _scoreUpdate(ball: Ball) { + const gc = this.components; + if (ball.pos.x > c.w) { + ++gc.scoreLeft; + } + else if (ball.pos.x < 0 - ball.width) { + ++gc.scoreRight; + } + this.playersMap.forEach( (client) => { + client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight))); + }); + this.spectatorsMap.forEach( (client) => { + client.socket.send(JSON.stringify(new ev.EventScoreUpdate(gc.scoreLeft, gc.scoreRight))); + }); + } + private _playersUpdate(s: GameSession) { + const gameState: ev.EventGameUpdate = s._gameStateSnapshot(); + s.playersMap.forEach( (client) => { + gameState.lastInputId = client.lastInputId; + client.socket.send(JSON.stringify(gameState)); + }); + } + private _spectatorsUpdate(s: GameSession) { + const gameState = s._gameStateSnapshot(); + s.spectatorsMap.forEach( (client) => { + client.socket.send(JSON.stringify(gameState)); + }); + } + private _gameStateSnapshot() : ev.EventGameUpdate { + const gc = this.components; + const snapshot = new ev.EventGameUpdate(); + snapshot.playerLeft.y = gc.playerLeft.pos.y; + snapshot.playerRight.y = gc.playerRight.pos.y; gc.ballsArr.forEach((ball) => { - update.ballsArr.push({ + snapshot.ballsArr.push({ x: ball.pos.x, y: ball.pos.y, dirX: ball.dir.x, @@ -139,15 +165,11 @@ export class GameSession { speed: ball.speed }); }); - if (s.matchOptions & en.MatchOptions.movingWalls) { - update.wallTop.y = gc.wallTop.pos.y; - update.wallBottom.y = gc.wallBottom.pos.y; + if (this.matchOptions & en.MatchOptions.movingWalls) { + snapshot.wallTop.y = gc.wallTop.pos.y; + snapshot.wallBottom.y = gc.wallBottom.pos.y; } - - s.playersMap.forEach( (client) => { - update.lastInputId = client.lastInputId; - client.socket.send(JSON.stringify(update)); - }); + return (snapshot); } private _newRound(s: GameSession, ball: Ball) { if (s._checkDisconnexions()) { @@ -187,6 +209,9 @@ export class GameSession { console.log("Player Right WIN (by forfeit)"); } luckyWinner.socket.send(JSON.stringify(eventEnd)); + this.spectatorsMap.forEach( (client) => { + client.socket.send(JSON.stringify(eventEnd)); + }); } return true; } @@ -209,5 +234,8 @@ export class GameSession { s.playersMap.forEach( (client) => { client.socket.send(JSON.stringify(eventEnd)); }); + s.spectatorsMap.forEach( (client) => { + client.socket.send(JSON.stringify(eventEnd)); + }); } } diff --git a/src/server/constants.ts b/src/server/constants.ts index bd5f8e9e..b7efffd3 100644 --- a/src/server/constants.ts +++ b/src/server/constants.ts @@ -6,4 +6,5 @@ export const serverGameLoopIntervalMS = 15; // millisecond export const fixedDeltaTime = serverGameLoopIntervalMS/1000; // second // 33.333ms == 1000/30 -export const clientsUpdateIntervalMS = 1000/30; // millisecond +export const playersUpdateIntervalMS = 1000/30; // millisecond +export const spectatorsUpdateIntervalMS = 1000/30; // millisecond diff --git a/src/server/wsServer.ts b/src/server/wsServer.ts index 73604b67..7a7e9e77 100644 --- a/src/server/wsServer.ts +++ b/src/server/wsServer.ts @@ -10,9 +10,10 @@ import { v4 as uuidv4 } from 'uuid'; import * as en from "../shared_js/enums.js" import * as ev from "../shared_js/class/Event.js" -import { Client, ClientPlayer } from "./class/Client.js" +import { Client, ClientPlayer, ClientSpectator } from "./class/Client.js" import { GameSession } from "./class/GameSession.js" import { shortId } from "./utils.js"; +import { gameSessionIdPLACEHOLDER } from "./constants.js"; // pas indispensable d'avoir un autre port si le WebSocket est relié à un serveur http préexistant ? const wsPort = 8042; @@ -61,16 +62,29 @@ function clientAnnounceListener(this: WebSocket, data: string) if (msg.type === en.EventTypes.clientAnnounce) { // TODO: reconnection with msg.clientId ? - // TODO: spectator/player distinction with msg.role ? - // msg.role is probably not a good idea. - // something like a different route could be better - // "/pong" to play, "/ID_OF_A_GAMESESSION" to spectate - - const player = clientsMap.get(this.id) as ClientPlayer; - player.matchOptions = msg.matchOptions; - this.send(JSON.stringify( new ev.EventAssignId(this.id) )); - this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) )); - matchmaking(player); + // "/pong" to play, "/pong?ID_OF_A_GAMESESSION" to spectate (or something like that) + if (msg.role === en.ClientRole.player) + { + const announce: ev.ClientAnnouncePlayer = msg; + const player = clientsMap.get(this.id) as ClientPlayer; + player.matchOptions = announce.matchOptions; + this.send(JSON.stringify( new ev.EventAssignId(this.id) )); + this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchmakingInProgress) )); + matchmaking(player); + } + else if (msg.role === en.ClientRole.spectator) + { + const announce: ev.ClientAnnounceSpectator = msg; + const gameSession = gameSessionsMap.get(announce.gameSessionId); + if (!gameSession) { + // WIP: send "invalid game session" + return; + } + const spectator = clientsMap.get(this.id) as ClientSpectator; + spectator.gameSession = gameSession; + gameSession.spectatorsMap.set(spectator.id, spectator); + this.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) )); + } } else { console.log("Invalid ClientAnnounce"); @@ -107,6 +121,7 @@ function matchmaking(player: ClientPlayer) return; } + // const id = gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR const id = uuidv4(); const gameSession = new GameSession(id, matchOptions); gameSessionsMap.set(id, gameSession); @@ -226,7 +241,8 @@ function clientTerminate(client: Client) client.gameSession.playersMap.delete(client.id); if (client.gameSession.playersMap.size === 0) { - clearInterval(client.gameSession.clientsUpdateInterval); + clearInterval(client.gameSession.playersUpdateInterval); + clearInterval(client.gameSession.spectatorsUpdateInterval); clearInterval(client.gameSession.gameLoopInterval); gameSessionsMap.delete(client.gameSession.id); } diff --git a/src/shared_js/class/Event.ts b/src/shared_js/class/Event.ts index 29ed9933..992827e4 100644 --- a/src/shared_js/class/Event.ts +++ b/src/shared_js/class/Event.ts @@ -82,16 +82,30 @@ export class ClientEvent { export class ClientAnnounce extends ClientEvent { role: en.ClientRole; - clientId: string; - matchOptions: en.MatchOptions; - constructor(role: en.ClientRole, matchOptions: en.MatchOptions, clientId: string = "") { + constructor(role: en.ClientRole) { super(en.EventTypes.clientAnnounce); this.role = role; + } +} + +export class ClientAnnouncePlayer extends ClientAnnounce { + clientId: string; + matchOptions: en.MatchOptions; + constructor(matchOptions: en.MatchOptions, clientId: string = "") { + super(en.ClientRole.player); this.clientId = clientId; this.matchOptions = matchOptions; } } +export class ClientAnnounceSpectator extends ClientAnnounce { + gameSessionId: string; + constructor(gameSessionId: string) { + super(en.ClientRole.spectator); + this.gameSessionId = gameSessionId; + } +} + export class EventInput extends ClientEvent { input: en.InputEnum; id: number; diff --git a/src/shared_js/constants.ts b/src/shared_js/constants.ts index ae3320e5..be86ba7b 100644 --- a/src/shared_js/constants.ts +++ b/src/shared_js/constants.ts @@ -24,3 +24,7 @@ export const newRoundDelay = 1500; // millisecond export const multiBallsCount = 3; export const movingWallPosMax = Math.floor(w*0.12); export const movingWallSpeed = Math.floor(w*0.08); + + +export const gameSessionIdPLACEHOLDER = "42"; // TESTING SPECTATOR PLACEHOLDER +// for testing, force gameSession.id in wsServer.ts->matchmaking() \ No newline at end of file