2024-08-30 02:56:03 +00:00
|
|
|
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,
|
2024-08-30 14:18:07 +00:00
|
|
|
xv: client.player.xv,
|
|
|
|
yv: client.player.yv,
|
2024-08-30 02:56:03 +00:00
|
|
|
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,
|
2024-08-30 11:30:53 +00:00
|
|
|
xv: prop.xv,
|
|
|
|
yv: prop.yv,
|
2024-08-30 02:56:03 +00:00
|
|
|
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("<", "<")
|
|
|
|
data.msg = data.msg.replaceAll(">", ">")
|
|
|
|
data.msg = data.msg.replaceAll("\n", "");
|
|
|
|
data.msg = data.msg.trim();
|
|
|
|
if (data.msg == "") return;
|
2024-08-30 11:33:40 +00:00
|
|
|
Log.info('<' + socket.player.name + '> ' + data.msg)
|
2024-08-30 02:56:03 +00:00
|
|
|
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;
|
2024-08-30 14:18:07 +00:00
|
|
|
last_update = performance.now();
|
2024-08-30 02:56:03 +00:00
|
|
|
|
|
|
|
// 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,
|
2024-08-30 14:18:07 +00:00
|
|
|
xv: client.player.xv,
|
|
|
|
yv: client.player.yv,
|
2024-08-30 02:56:03 +00:00
|
|
|
};
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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,
|
2024-08-30 11:30:53 +00:00
|
|
|
xv: prop.xv,
|
|
|
|
yv: prop.yv,
|
2024-08-30 02:56:03 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|