import { WebSocketServer } from "ws"; import Log from "../common/log.js"; import { WORLD_SIZE } from "../common/world.js"; import Player from "../common/player.js"; import Prop from "../common/prop.js"; const TICK_RATE = 30; var clients = []; var props = { 1: new Prop("the silly", WORLD_SIZE / 2, WORLD_SIZE / 2, "#ff00ff", "/img/ball.png") }; var last_update = 0.0; var ticks = 0; var wss; export function init(http_server) { wss = new WebSocketServer({ server: http_server }); wss.on("connection", (socket) => { clients.push(socket); socket.on("error", error => { Log.warn("Websocket connection closed due to error: " + error); if (socket.player) { broadcast({ type: "leave", id: socket.id, }); } clients = clients.filter(s => s != socket); }); socket.on("close", () => { if (socket.player) { Log.info(socket.player.name + " left the game."); broadcast({ type: "leave", id: socket.id, }); } clients = clients.filter(s => s != socket); }); socket.on("message", msg => { try { const data = JSON.parse(msg); if (!data.type) throw new Error("Type not specified"); switch (data.type) { case "join": if (!data.name) throw new Error("Name cannot be null"); var player_name = data.name.slice(0, 32); player_name = player_name.replaceAll('<', '<'); player_name = player_name.replaceAll('>', '>'); player_name = player_name.trim(); socket.id = generateID(); socket.player = new Player( data.name.slice(0, 32), WORLD_SIZE / 2, WORLD_SIZE / 2, randomColour() ); Log.info(socket.player.name + " joined the game."); var lobby_players = {}; clients.forEach(client => { if (!client.player) return; lobby_players[client.id] = { name: client.player.name, x: client.player.x, y: client.player.y, col: client.player.colour, }; }); var lobby_props = {}; Object.keys(props).forEach(id => { const prop = props[id]; lobby_props[id] = { name: prop.name, x: prop.x, y: prop.y, xv: prop.xv, yv: prop.yv, col: prop.colour, sprite: prop.sprite, } }); socket.send(JSON.stringify({ type: "welcome", id: socket.id, tick: ticks, players: lobby_players, props: lobby_props, })); clients.forEach(s => { if (s.id == socket.id) return; // send player joined event s.send(JSON.stringify({ type: "join", id: socket.id, name: socket.player.name, x: socket.player.x, y: socket.player.y, col: socket.player.colour, })); }); break; case "update": if (!socket.player) throw new Error("Player does not exist"); if (data.x === undefined || data.y === undefined) throw new Error("Movement vector not provided"); if (data.tick === undefined) throw new Error("User tick not provided"); socket.player.tick = data.tick; socket.player.in_x = Math.min(1.0, data.x); socket.player.in_y = Math.min(1.0, data.y); break; case "chat": if (data.msg === undefined) throw new Error("Attempted chat with no message"); Log.info('<' + socket.player.name + '> ' + data.msg) data.msg = data.msg.replaceAll("<", "<") data.msg = data.msg.replaceAll(">", ">") data.msg = data.msg.replaceAll("\n", ""); data.msg = data.msg.trim(); if (data.msg == "") return; clients.forEach(client => { client.send(JSON.stringify({ type: "chat", player: socket.id, msg: data.msg, })); }); break; default: throw new Error("Invalid message type"); } } catch (error) { if (socket.player) { Log.warn("Received invalid packet from " + socket.player.id + ": " + error); socket.send(JSON.stringify({ type: "kick", reason: "Received invalid packet", })); } else { Log.warn("Received invalid packet: " + error); } socket.close(); } }); }); update(); } function update() { var delta = (performance.now() - last_update) / 1000; // update players var frame_players = {}; clients.forEach(client => { if (!client.player) return; client.player.update(delta); client.player.x = Math.max(Math.min(client.player.x, WORLD_SIZE), 0); client.player.y = Math.max(Math.min(client.player.y, WORLD_SIZE), 0); frame_players[client.id] = { x: client.player.x, y: client.player.y, }; }); // god help me this code is awful // really leaning on this just being a tech demo here var prop_players = []; clients.forEach(client => { if (client.player) prop_players.push(client.player); }); var frame_props = {}; Object.keys(props).forEach(id => { const prop = props[id]; prop.update(delta, prop_players); frame_props[id] = { x: prop.x, y: prop.y, xv: prop.xv, yv: prop.yv, } }); // send update to players clients.forEach(client => { if (!client.player) return; client.send(JSON.stringify({ type: "update", tick: client.player.tick, players: frame_players, props: frame_props, })); }) last_update = performance.now(); ticks++; setTimeout(update, 1000 / TICK_RATE); } function broadcast(data) { clients.forEach(socket => { socket.send(JSON.stringify(data)); }); } function generateID() { // five random digits followed by five digits from the end of unix timestamp return (10000 + Math.floor(Math.random() * 90000)).toString() + (new Date() % 100000).toString(); } function randomColour() { var res = "#"; for (var i = 0; i < 6; i++) res += "0123456789abcdef"[Math.floor(Math.random() * 16)]; return res; }