267 lines
7.5 KiB
TypeScript
267 lines
7.5 KiB
TypeScript
|
|
import { WebSocketServer, WebSocket as BaseLibWebSocket } from "ws";
|
|
|
|
export class WebSocket extends BaseLibWebSocket {
|
|
id?: string;
|
|
}
|
|
|
|
import { IncomingMessage } from "http";
|
|
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, 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;
|
|
export const wsServer = new WebSocketServer<WebSocket>({port: wsPort, path: "/pong"});
|
|
|
|
const clientsMap: Map<string, Client> = new Map; // socket.id/Client
|
|
const matchmakingPlayersMap: Map<string, ClientPlayer> = new Map; // socket.id/ClientPlayer (duplicates with clientsMap)
|
|
const gameSessionsMap: Map<string, GameSession> = new Map; // GameSession.id(url)/GameSession
|
|
|
|
wsServer.on("connection", connectionListener);
|
|
wsServer.on("error", errorListener);
|
|
wsServer.on("close", closeListener);
|
|
|
|
|
|
function connectionListener(socket: WebSocket, request: IncomingMessage)
|
|
{
|
|
const id = uuidv4();
|
|
const client = new Client(socket, id);
|
|
clientsMap.set(id, client);
|
|
socket.id = id;
|
|
|
|
socket.on("pong", function heartbeat() {
|
|
client.isAlive = true;
|
|
// console.log(`client ${shortId(client.id)} is alive`);
|
|
});
|
|
|
|
socket.on("message", function log(data: string) {
|
|
try {
|
|
const event: ev.ClientEvent = JSON.parse(data);
|
|
if (event.type === en.EventTypes.clientInput) {
|
|
return;
|
|
}
|
|
}
|
|
catch (e) {}
|
|
console.log("data: " + data);
|
|
});
|
|
|
|
socket.once("message", clientAnnounceListener);
|
|
}
|
|
|
|
|
|
function clientAnnounceListener(this: WebSocket, data: string)
|
|
{
|
|
try {
|
|
const msg : ev.ClientAnnounce = JSON.parse(data);
|
|
if (msg.type === en.EventTypes.clientAnnounce)
|
|
{
|
|
// TODO: reconnection with msg.clientId ?
|
|
// "/pong" to play, "/pong?ID_OF_A_GAMESESSION" to spectate (or something like that)
|
|
if (msg.role === en.ClientRole.player)
|
|
{
|
|
const announce: ev.ClientAnnouncePlayer = <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 = <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");
|
|
}
|
|
return;
|
|
}
|
|
catch (e) {
|
|
console.log("Invalid JSON (clientAnnounceListener)");
|
|
}
|
|
this.once("message", clientAnnounceListener);
|
|
}
|
|
|
|
|
|
function matchmaking(player: ClientPlayer)
|
|
{
|
|
const minPlayersNumber = 2;
|
|
const maxPlayersNumber = 2;
|
|
const matchOptions = player.matchOptions;
|
|
matchmakingPlayersMap.set(player.id, player);
|
|
|
|
const compatiblePlayers: ClientPlayer[] = [];
|
|
for (const [id, client] of matchmakingPlayersMap)
|
|
{
|
|
if (client.matchOptions === matchOptions)
|
|
{
|
|
compatiblePlayers.push(client);
|
|
if (compatiblePlayers.length === maxPlayersNumber) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (compatiblePlayers.length < minPlayersNumber) {
|
|
return;
|
|
}
|
|
|
|
// const id = gameSessionIdPLACEHOLDER; // Force ID, TESTING SPECTATOR
|
|
const id = uuidv4();
|
|
const gameSession = new GameSession(id, matchOptions);
|
|
gameSessionsMap.set(id, gameSession);
|
|
|
|
compatiblePlayers.forEach((client) => {
|
|
matchmakingPlayersMap.delete(client.id);
|
|
client.gameSession = gameSession;
|
|
gameSession.playersMap.set(client.id, client);
|
|
gameSession.unreadyPlayersMap.set(client.id, client);
|
|
});
|
|
|
|
// WIP: Not pretty, hardcoded two players.
|
|
// Could be done in gameSession maybe ?
|
|
compatiblePlayers[0].racket = gameSession.components.playerRight;
|
|
compatiblePlayers[1].racket = gameSession.components.playerLeft;
|
|
|
|
compatiblePlayers.forEach((client) => {
|
|
client.socket.once("message", playerReadyConfirmationListener);
|
|
});
|
|
|
|
compatiblePlayers[0].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.right) ));
|
|
compatiblePlayers[1].socket.send(JSON.stringify( new ev.EventMatchmakingComplete(en.PlayerSide.left) ));
|
|
|
|
setTimeout(function abortMatch() {
|
|
if (gameSession.unreadyPlayersMap.size !== 0)
|
|
{
|
|
gameSessionsMap.delete(gameSession.id);
|
|
gameSession.playersMap.forEach((client) => {
|
|
client.socket.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchAbort) ));
|
|
client.gameSession = null;
|
|
clientTerminate(client);
|
|
});
|
|
}
|
|
}, 5000);
|
|
}
|
|
|
|
|
|
function playerReadyConfirmationListener(this: WebSocket, data: string)
|
|
{
|
|
try {
|
|
const msg : ev.ClientEvent = JSON.parse(data);
|
|
if (msg.type === en.EventTypes.clientPlayerReady)
|
|
{
|
|
const client = clientsMap.get(this.id);
|
|
const gameSession = client.gameSession;
|
|
gameSession.unreadyPlayersMap.delete(this.id);
|
|
if (gameSession.unreadyPlayersMap.size === 0) {
|
|
gameSession.playersMap.forEach( (client) => {
|
|
client.socket.send(JSON.stringify( new ev.ServerEvent(en.EventTypes.matchStart) ));
|
|
});
|
|
gameSession.start();
|
|
}
|
|
}
|
|
else {
|
|
console.log("Invalid playerReadyConfirmation");
|
|
}
|
|
return;
|
|
}
|
|
catch (e) {
|
|
console.log("Invalid JSON (playerReadyConfirmationListener)");
|
|
}
|
|
this.once("message", playerReadyConfirmationListener);
|
|
}
|
|
|
|
|
|
export function clientInputListener(this: WebSocket, data: string)
|
|
{
|
|
try {
|
|
// const input: ev.ClientEvent = JSON.parse(data);
|
|
const input: ev.EventInput = JSON.parse(data);
|
|
if (input.type === en.EventTypes.clientInput)
|
|
{
|
|
const client = clientsMap.get(this.id) as ClientPlayer;
|
|
client.inputBuffer = input;
|
|
client.gameSession.instantInputDebug(client); // wip
|
|
}
|
|
else {
|
|
console.log("Invalid clientInput");
|
|
}
|
|
}
|
|
catch (e) {
|
|
console.log("Invalid JSON (clientInputListener)");
|
|
}
|
|
}
|
|
|
|
////////////
|
|
////////////
|
|
|
|
const pingInterval = setInterval( () => {
|
|
let deleteLog = "";
|
|
clientsMap.forEach( (client) => {
|
|
if (!client.isAlive) {
|
|
clientTerminate(client);
|
|
deleteLog += ` ${shortId(client.id)} |`;
|
|
}
|
|
else {
|
|
client.isAlive = false;
|
|
client.socket.ping();
|
|
}
|
|
});
|
|
|
|
if (deleteLog) {
|
|
console.log(`Disconnected:${deleteLog}`);
|
|
}
|
|
console.log("gameSessionMap size: " + gameSessionsMap.size);
|
|
console.log("clientsMap size: " + clientsMap.size);
|
|
console.log("matchmakingPlayersMap size: " + matchmakingPlayersMap.size);
|
|
console.log("");
|
|
}, 4200);
|
|
|
|
|
|
function clientTerminate(client: Client)
|
|
{
|
|
client.socket.terminate();
|
|
if (client.gameSession)
|
|
{
|
|
client.gameSession.playersMap.delete(client.id);
|
|
if (client.gameSession.playersMap.size === 0)
|
|
{
|
|
clearInterval(client.gameSession.playersUpdateInterval);
|
|
clearInterval(client.gameSession.spectatorsUpdateInterval);
|
|
clearInterval(client.gameSession.gameLoopInterval);
|
|
gameSessionsMap.delete(client.gameSession.id);
|
|
}
|
|
}
|
|
clientsMap.delete(client.id);
|
|
if (matchmakingPlayersMap.has(client.id)) {
|
|
matchmakingPlayersMap.delete(client.id);
|
|
}
|
|
}
|
|
|
|
|
|
function closeListener()
|
|
{
|
|
clearInterval(pingInterval);
|
|
}
|
|
|
|
|
|
function errorListener(error: Error)
|
|
{
|
|
console.log("Error: " + JSON.stringify(error));
|
|
}
|