Files
42_INT_14_transcendence/src/client/ws.ts
2022-12-08 04:08:16 +01:00

203 lines
6.3 KiB
TypeScript

import * as c from "./constants.js"
import { gc, matchOptions } from "./global.js"
import * as ev from "../shared_js/class/Event.js"
import * as en from "../shared_js/enums.js"
import { matchmaking, matchmakingComplete, matchAbort, matchStart } from "./pong.js";
import { RacketClient } from "./class/RectangleClient.js";
import { repeatInput } from "./handleInput.js";
import { soundRoblox } from "./audio.js"
import { sleep } from "./utils.js";
import { Vector, VectorInteger } from "../shared_js/class/Vector.js";
class ClientInfo {
id = "";
side: en.PlayerSide;
racket: RacketClient;
opponent: RacketClient;
opponentNextPos: 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 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.addEventListener("message", logListener); // for testing purpose
socket.addEventListener("message", preMatchListener);
}
function logListener(this: WebSocket, event: MessageEvent) {
console.log("%i: " + event.data, Date.now());
}
function preMatchListener(this: WebSocket, event: MessageEvent)
{
const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) {
case en.EventTypes.assignId:
clientInfo.id = (<ev.EventAssignId>data).id;
break;
case en.EventTypes.matchmakingInProgress:
matchmaking();
break;
case en.EventTypes.matchmakingComplete:
clientInfo.side = (<ev.EventMatchmakingComplete>data).side;
if (clientInfo.side === en.PlayerSide.left)
{
clientInfo.racket = gc.playerLeft;
clientInfo.opponent = gc.playerRight;
}
else if (clientInfo.side === en.PlayerSide.right)
{
clientInfo.racket = gc.playerRight;
clientInfo.opponent = gc.playerLeft;
}
clientInfo.opponentNextPos = new VectorInteger(clientInfo.opponent.pos.x, clientInfo.opponent.pos.y);
clientInfo.racket.color = "darkgreen"; // for testing purpose
socket.send(JSON.stringify( new ev.ClientEvent(en.EventTypes.clientPlayerReady) )); // TODO: set an interval/timeout to resend until matchStart response (in case of network problem)
matchmakingComplete();
break;
case en.EventTypes.matchStart:
socket.removeEventListener("message", preMatchListener);
socket.addEventListener("message", inGameListener);
matchStart();
break;
case en.EventTypes.matchAbort:
socket.removeEventListener("message", preMatchListener);
matchAbort();
break;
}
}
function inGameListener(event: MessageEvent)
{
const data: ev.ServerEvent = JSON.parse(event.data);
switch (data.type) {
case en.EventTypes.gameUpdate:
// setTimeout(gameUpdate, 500, data as ev.EventGameUpdate); // artificial latency for testing purpose
gameUpdate(data as ev.EventGameUpdate);
break;
case en.EventTypes.scoreUpdate:
scoreUpdate(data as ev.EventScoreUpdate);
break;
case en.EventTypes.matchEnd:
matchEnd(data as ev.EventMatchEnd);
break;
}
}
function gameUpdate(data: ev.EventGameUpdate)
{
console.log("gameUpdate");
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;
});
/* // Equivalent to
gc.ballsArr.forEach((ball, i) => {
ball.pos.assign(data.ballsArr[i].x, data.ballsArr[i].y);
ball.dir.assign(data.ballsArr[i].dirX, data.ballsArr[i].dirY);
ball.speed = data.ballsArr[i].speed;
}); */
const predictionPos = new VectorInteger(clientInfo.racket.pos.x, clientInfo.racket.pos.y); // debug
if (clientInfo.side === en.PlayerSide.left) {
clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerLeft.y);
}
else if (clientInfo.side === en.PlayerSide.right) {
clientInfo.racket.pos.assign(clientInfo.racket.pos.x, data.playerRight.y);
}
// interpolation
clientInfo.opponent.pos.assign(clientInfo.opponentNextPos.x, clientInfo.opponentNextPos.y);
if (clientInfo.side === en.PlayerSide.left) {
clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerRight.y);
}
else if (clientInfo.side === en.PlayerSide.right) {
clientInfo.opponentNextPos.assign(clientInfo.opponent.pos.x, data.playerLeft.y);
}
clientInfo.opponent.dir = new Vector(
clientInfo.opponentNextPos.x - clientInfo.opponent.pos.x,
clientInfo.opponentNextPos.y - clientInfo.opponent.pos.y
);
if (Math.abs(clientInfo.opponent.dir.x) + Math.abs(clientInfo.opponent.dir.y) !== 0) {
clientInfo.opponent.dir = clientInfo.opponent.dir.normalized();
}
// server reconciliation
repeatInput(data.lastInputId);
// debug
if (clientInfo.racket.pos.y > predictionPos.y + 1
|| clientInfo.racket.pos.y < predictionPos.y - 1)
{
console.log(
`Reconciliation error:
server y: ${data.playerLeft.y}
reconciliation y: ${clientInfo.racket.pos.y}
prediction y: ${predictionPos.y}`
);
}
}
function scoreUpdate(data: ev.EventScoreUpdate)
{
// console.log("scoreUpdate");
if (clientInfo.side === en.PlayerSide.left && data.scoreRight > gc.scoreRight.value) {
soundRoblox.play();
}
else if (clientInfo.side === en.PlayerSide.right && data.scoreLeft > gc.scoreLeft.value) {
soundRoblox.play();
}
gc.scoreLeft.value = data.scoreLeft;
gc.scoreRight.value = data.scoreRight;
}
function matchEnd(data: ev.EventMatchEnd)
{
if (data.winner === clientInfo.side) {
gc.text1.pos.assign(c.w*0.415, c.h*0.5);
gc.text1.text = "WIN";
if (data.forfeit) {
if (clientInfo.side === en.PlayerSide.left) {
gc.text2.pos.assign(c.w*0.65, c.h*0.42);
gc.text3.pos.assign(c.w*0.65, c.h*0.52);
}
else {
gc.text2.pos.assign(c.w*0.09, c.h*0.42);
gc.text3.pos.assign(c.w*0.09, c.h*0.52);
}
setTimeout(() => {
gc.text2.text = "par forfait";
}, 2500);
setTimeout(() => {
gc.text3.text = "calme ta joie";
}, 5000);
}
}
else {
gc.text1.pos.assign(c.w*0.383, c.h*0.5);
gc.text1.text = "LOSE";
}
// matchEnded = true; // unused
}
// export let matchEnded = false; // unused