189 lines
5.8 KiB
TypeScript
189 lines
5.8 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 * 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"
|
|
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:
|
|
msg.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)
|
|
msg.matchmakingComplete();
|
|
break;
|
|
case en.EventTypes.matchStart:
|
|
socket.removeEventListener("message", preMatchListener);
|
|
socket.addEventListener("message", inGameListener);
|
|
start();
|
|
break;
|
|
case en.EventTypes.matchAbort:
|
|
socket.removeEventListener("message", preMatchListener);
|
|
msg.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) {
|
|
msg.win();
|
|
if (data.forfeit) {
|
|
msg.forfeit(clientInfo.side);
|
|
}
|
|
}
|
|
else {
|
|
msg.lose();
|
|
}
|
|
// matchEnded = true; // unused
|
|
}
|
|
|
|
// export let matchEnded = false; // unused
|