things + stff

This commit is contained in:
Kim Ravn Hansen
2025-09-07 23:24:50 +02:00
parent fb915f2681
commit 8a4eb25507
27 changed files with 1991 additions and 630 deletions

View File

@@ -2,255 +2,183 @@ import WebSocket, { WebSocketServer } from "ws";
import http from "http";
import path from "path";
import fs from "fs";
import { Player } from "./models/player.js";
import { Game } from "./models/game.js";
import { ClientMessage, MSG_ERROR, MSG_MESSAGE, MSG_PROMPT, MSG_CALAMITY, } from "./utils/messages.js";
class Session {
/** @type {boolean} */
usernameProcessed = false;
/** @type {boolean} */
passwordProcessed = false;
/** @type {boolean} */
ready = false;
/** @type Date */
latestPing;
/** @type {Player} */
player;
}
import * as msg from "./utils/messages.js";
import * as cfg from "./utils/config.js";
import { Session } from "./models/session.js";
import { Seeder } from "./seed.js";
import { AuthState } from "./models/states/auth.js";
class MudServer {
/** @type {Map<WebSocket,Session>} */
sessions = new Map();
/** @type {Game} */
game = new Game();
/**
* Send a message via a websocket.
*
* @param {WebSocket} websocket
* @param {string|number} messageType
* @param {...any} args
*/
send(websocket, messageType, ...args) {
// create array consisting of [messageType, args[0], args[1], ... ];
websocket.send(JSON.stringify([messageType, ...args]));
constructor() {
/** @type {Game} */
this.game = new Game();
if (cfg.DEV) {
(new Seeder()).seed(this.game);
}
}
/**
* @param {WebSocket} websocket
*/
// ____ ___ _ _ _ _ _____ ____ _____ _____ ____
// / ___/ _ \| \ | | \ | | ____/ ___|_ _| ____| _ \
// | | | | | | \| | \| | _|| | | | | _| | | | |
// | |__| |_| | |\ | |\ | |__| |___ | | | |___| |_| |
// \____\___/|_| \_|_| \_|_____\____| |_| |_____|____/
//------------------------------------------------------
// Handle New Socket Connections
//------------------------------
/** @param {WebSocket} websocket */
onConnectionEstabished(websocket) {
console.log("New connection established");
this.sessions[websocket] = new Session();
const session = new Session(websocket, this.game);
session.sendSystemMessage("dev", true)
websocket.on("message", (data) => {
this.onIncomingMessage(websocket, data);
});
// ____ _ ___ ____ _____
// / ___| | / _ \/ ___|| ____|
// | | | | | | | \___ \| _|
// | |___| |__| |_| |___) | |___
// \____|_____\___/|____/|_____|
//-------------------------------
// Handle Socket Closing
//----------------------
websocket.on("close", () => {
this.onConnectionClosed(websocket);
if (!session.player) {
console.info("A player without a session disconnected");
return;
}
//-------------
// TODO
//-------------
// Handle player logout (move the or hide their characters)
//
// Maybe session.onConnectionClosed() that calls session._state.onConnectionClosed()
// Maybe this.setState(new ConnectionClosedState());
// Maybe both ??
console.log(`Player ${session.player.username} disconnected`);
});
this.send(websocket, MSG_MESSAGE, "Welcome to MUUUHD", "big");
this.send(websocket, MSG_PROMPT, "Please enter your username");
// __ __ _____ ____ ____ _ ____ _____
// | \/ | ____/ ___/ ___| / \ / ___| ____|
// | |\/| | _| \___ \___ \ / _ \| | _| _|
// | | | | |___ ___) |__) / ___ \ |_| | |___
// |_| |_|_____|____/____/_/ \_\____|_____|
//--------------------------------------------
// HANDLE INCOMING MESSAGES
//-------------------------
websocket.on("message", (data) => {
try {
console.debug("incoming websocket message %s", data);
if (!session.state) {
console.error("we received a message, but don't even have a state. Zark!");
websocket.send(msg.prepare(msg.ERROR, "Oh no! I don't know what to do!?"));
return;
}
const msgObj = new msg.ClientMessage(data.toString());
if (msgObj.isQuitCommand()) {
//---------------------
// TODO TODO TODO TODO
//---------------------
// Set state = QuitState
//
websocket.send(msg.prepare(msg.MESSAGE, "The quitting quitter quits... Typical. Cya!"));
websocket.close();
return;
}
if (typeof session.state.onMessage !== "function") {
console.error("we received a message, but we're not i a State to receive it");
websocket.send(msg.prepare(msg.ERROR, "Oh no! I don't know what to do with that message."));
return;
}
session.state.onMessage(msgObj);
} catch (error) {
console.trace("received an invalid message (error: %s)", error, data.toString(), data);
websocket.send(msg.prepare(
msg.CALAMITY,
error
));
}
});
session.setState(new AuthState(session));
}
/**
* @param {WebSocket} websocket
* @param {strings} data
*/
onIncomingMessage(websocket, data) {
const session = this.sessions.get(websocket);
if (!session) {
console.error(
"Incoming message from a client without a session!",
data,
);
this.send(
websocket,
MSG_ERROR,
"terminal",
"You do not have an active session. Go away!",
);
websocket.close();
return;
}
let message;
try {
message = new ClientMessage(data);
} catch (error) {
console.error("Bad websocket message", data, error);
this.send(
websocket,
MSG_ERROR,
"terminal",
"You sent me a bad message! Goodbye...",
);
websocket.close();
return;
}
if (!session.usernameProcessed) {
//
//----------------------------------------------------
// We haven"t gotten a username yet, so we expect one.
//----------------------------------------------------
if (!message.hasUsername()) {
console.error("User should have sent a “username” message, but sent something else instead");
this.send(websocket, MSG_CALAMITY, "I expected you to send me a username, but you sent me something else instead. You bad! Goodbye...");
// for now, just close the socket.
websocket.close();
}
const player = this.game.players.get(message.username);
if (!player) {
//----------------------------------------------------
// Invalid Username.
//----------------------------------------------------
console.log("Invalid username sent during login: %s", username);
this.send(websocket, MSG_ERROR, "Invalid username");
this.send(
websocket,
MSG_PROMPT,
"Please enter a valid username",
);
}
// correct username, tentatively assign player to session
// even though we have not yet validated the password.
session.player = player;
session.usernameProcessed = true;
this.send(websocket, MSG_MESSAGE, "Username received");
this.send(websocket, MSG_PROMPT, "Enter your password");
return;
}
// ____ _____ _ ____ _____
// / ___|_ _|/ \ | _ \_ _|
// \___ \ | | / _ \ | |_) || |
// ___) || |/ ___ \| _ < | |
// |____/ |_/_/ \_\_| \_\|_|
//-----------------------------
// Start the server
//-----------------
start() {
//
//----------------------------------------------------
// The player has entered a valid username, now expect
// a password.
//----------------------------------------------------
if (!session.passwordProcessed) {
if (!message.hasPassword) {
console.error(
"Youser should have sent a “password” message, but sent this instead: %s",
message.type,
);
}
}
// The file types we allow to be served.
const contentTypes = {
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
};
//
//----------------------------------------------------
// Process the player's commands
//----------------------------------------------------
if (message.isCommand()) {
// switch case for commands.
return;
}
console.error(
"We have received a message we couldn't handle!!!",
message,
);
}
/**
*
* @param {WebSocket} websocket
* @param {string} name
*/
createPlayer(websocket, name) {
const player = new Player(name, websocket);
this.players.set(websocket, player);
this.players.set(name, player);
const startRoom = this.rooms.get("town_square");
startRoom.addPlayer(player);
player.sendMessage(`Welcome, ${name}! You have entered the world.`);
this.showRoom(player);
}
/**
* Called when a websocket connection is closing.
*
* @param {WebSocket} websocket
*/
onConnectionClosed(websocket) {
const session = this.sessions.get(websocket);
if (session && session.player) {
console.log(`Player ${player.username} disconnected`);
// Create HTTP server for serving the client - Consider moving to own file
const httpServer = http.createServer((req, res) => {
let filePath = path.join("public", req.url === "/" ? "index.html" : req.url);
const ext = path.extname(filePath);
const contentType = contentTypes[ext];
//
// Handle player logout (move the or hide their characters)
// this.game.playerLoggedOut();
} else {
console.log("A player without a session disconnected");
}
// Check if the requested file has a legal file type.
if (!contentType) {
// Invalid file, pretend it did not exist!
res.writeHead(404);
res.end(`File not found`);
console.log("Bad http request", req.url);
return;
}
this.sessions.delete(websocket);
//
// Check if the file exists.
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end(`File not found`);
console.log("Bad http request", req.url);
return;
}
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
});
});
//
// Create WebSocket server
const websocketServer = new WebSocketServer({ server: httpServer });
websocketServer.on("connection", (ws) => {
this.onConnectionEstabished(ws);
});
console.info(`running in ${cfg.ENV} mode`);
httpServer.listen(cfg.PORT, () => {
console.log(`NUUHD server running on port ${cfg.PORT}`);
console.log(`WebSocket server ready for connections`);
});
}
}
// Create HTTP server for serving the client
const httpServer = http.createServer((req, res) => {
// let filePath = path.join(__dirname, "public", req.url === "/" ? "index.html" : req.url);
let filePath = path.join(
"public",
req.url === "/" ? "index.html" : req.url,
);
const ext = path.extname(filePath);
const contentTypes = {
".js": "application/javascript",
".css": "text/css",
".html": "text/html",
};
if (!contentTypes[ext]) {
// Invalid file, pretend it did not exist!
res.writeHead(404);
res.end(`File ${filePath} not found (invalid $ext)`);
return;
}
const contentType = contentTypes[ext];
fs.readFile(filePath, (err, data) => {
if (err) {
res.writeHead(404);
res.end(`File ${filePath} . ${ext} not found (${err})`);
return;
}
res.writeHead(200, { "Content-Type": contentType });
res.end(data);
});
});
// Create WebSocket server
const websocketServer = new WebSocketServer({ server: httpServer });
const mudServer = new MudServer();
websocketServer.on("connection", (ws) => {
mudServer.onConnectionEstabished(ws);
});
// websocketServer.on("connection", mudServer.onConnectionEstabished);
const PORT = process.env.PORT || 3000;
httpServer.listen(PORT, () => {
console.log(`MUD server running on port ${PORT}`);
console.log(`WebSocket server ready for connections`);
});
// __ __ _ ___ _ _
// | \/ | / \ |_ _| \ | |
// | |\/| | / _ \ | || \| |
// | | | |/ ___ \ | || |\ |
// |_| |_/_/ \_\___|_| \_|
//---------------------------
// Code entry point
//-----------------
(new MudServer()).start();