multiplayer-test/server/ws.js

238 lines
8.2 KiB
JavaScript

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('<', '&lt;');
player_name = player_name.replaceAll('>', '&gt;');
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,
xv: client.player.xv,
yv: client.player.yv,
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");
data.msg = data.msg.replaceAll("<", "&lt;")
data.msg = data.msg.replaceAll(">", "&gt;")
data.msg = data.msg.replaceAll("\n", "");
data.msg = data.msg.trim();
if (data.msg == "") return;
Log.info('<' + socket.player.name + '> ' + data.msg)
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;
last_update = performance.now();
// update players
var frame_players = {};
clients.forEach(client => {
if (!client.player) return;
client.player.update(delta);
frame_players[client.id] = {
x: client.player.x,
y: client.player.y,
xv: client.player.xv,
yv: client.player.yv,
};
});
// 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,
}));
})
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;
}