Things and støff

This commit is contained in:
Kim Ravn Hansen
2025-09-14 20:43:06 +02:00
parent ed91a7f2f7
commit aeb9d776fc
25 changed files with 688 additions and 591 deletions

View File

@@ -1,9 +1,9 @@
import WebSocket from "ws"; import WebSocket from "ws";
import { Player } from "./player.js"; import { Player } from "./player.js";
import * as msg from "../utils/messages.js";
import { mustBeString, mustBe } from "../utils/mustbe.js"; import { mustBeString, mustBe } from "../utils/mustbe.js";
import { Scene } from "../scenes/scene.js"; import { Scene } from "../scenes/scene.js";
import { gGame } from "./globals.js"; import { gGame } from "./globals.js";
import { formatMessage, MessageType } from "../utils/messages.js";
export class Session { export class Session {
/** @type {WebSocket} */ /** @type {WebSocket} */
@@ -71,7 +71,7 @@ export class Session {
/** /**
* Send a message via our websocket. * Send a message via our websocket.
* *
* @param {string|number} messageType * @param {MessageType} messageType The message "header" (the first arg in the array sent to the client) holds the message type.
* @param {...any} args * @param {...any} args
*/ */
send(messageType, ...args) { send(messageType, ...args) {
@@ -79,7 +79,7 @@ export class Session {
console.error("Trying to send a message without a valid websocket", messageType, args); console.error("Trying to send a message without a valid websocket", messageType, args);
return; return;
} }
this._websocket.send(JSON.stringify([messageType, ...args])); this._websocket.send(formatMessage(messageType, ...args));
} }
/** /**
@@ -100,7 +100,7 @@ export class Session {
} }
this.send( this.send(
msg.PROMPT, // message type MessageType.PROMPT, // message type
text, // TODO: prompt text must be string or an array of strings text, // TODO: prompt text must be string or an array of strings
mustBe(options, "object"), mustBe(options, "object"),
); );
@@ -113,12 +113,12 @@ export class Session {
* @param {object?} options message options for the client. * @param {object?} options message options for the client.
*/ */
sendText(text, options = {}) { sendText(text, options = {}) {
this.send(msg.TEXT, text, options); this.send(MessageType.TEXT, text, options);
} }
/** @param {string|string[]} errorMessage */ /** @param {string|string[]} errorMessage */
sendError(errorMessage) { sendError(errorMessage, options = { verbatim: true }) {
this.send(msg.ERROR, mustBeString(errorMessage)); this.send(MessageType.ERROR, mustBeString(errorMessage), options);
} }
/** /**
@@ -128,15 +128,15 @@ export class Session {
calamity(errorMessage) { calamity(errorMessage) {
// //
// The client should know not to format calamaties anyway, but we add “preformatted” anyway // The client should know not to format calamaties anyway, but we add “preformatted” anyway
this.send(msg.CALAMITY, errorMessage, { preformatted: true }); this.send(MessageType.CALAMITY, errorMessage, { preformatted: true });
this.close(); this.close();
} }
/** /**
* @param {string} systemMessageType * @param {MessageType} systemMessageType
* @param {any?} value * @param {any?} value
*/ */
sendSystemMessage(systemMessageType, value = undefined) { sendSystemMessage(systemMessageType, value = undefined) {
this.send(msg.SYSTEM, mustBeString(systemMessageType), value); this.send(MessageType.SYSTEM, mustBeString(systemMessageType), value);
} }
} }

889
server/package-lock.json generated
View File

@@ -1,446 +1,453 @@
{ {
"name": "websocket-mud", "name": "websocket-mud",
"version": "1.0.0", "version": "1.0.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "websocket-mud", "name": "websocket-mud",
"version": "1.0.0", "version": "1.0.0",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"figlet": "^1.8.2", "figlet": "^1.8.2",
"ws": "^8.14.2" "sprintf-js": "^1.1.3",
}, "ws": "^8.14.2"
"devDependencies": { },
"nodemon": "^3.0.1", "devDependencies": {
"prettier": "3.6.2" "nodemon": "^3.0.1",
}, "prettier": "3.6.2"
"engines": { },
"node": ">=14.0.0" "engines": {
} "node": ">=14.0.0"
}, }
"node_modules/anymatch": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/figlet": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.8.2.tgz",
"integrity": "sha512-iPCpE9B/rOcjewIzDnagP9F2eySzGeHReX8WlrZQJkqFBk2wvq8gY0c6U6Hd2y9HnX1LQcYSeP7aEHoPt6sVKQ==",
"license": "MIT",
"bin": {
"figlet": "bin/index.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true,
"license": "ISC"
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nodemon": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
"integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^7.5.3",
"simple-update-notifier": "^2.0.0",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"bin": {
"nodemon": "bin/nodemon.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true,
"license": "MIT"
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
"dev": true,
"license": "ISC",
"bin": {
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true,
"license": "MIT"
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
}, },
"utf-8-validate": { "node_modules/anymatch": {
"optional": true "version": "3.1.3",
"resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
"integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
"dev": true,
"license": "ISC",
"dependencies": {
"normalize-path": "^3.0.0",
"picomatch": "^2.0.4"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/binary-extensions": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
"integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/brace-expansion": {
"version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/braces": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true,
"license": "MIT",
"dependencies": {
"fill-range": "^7.1.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/chokidar": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
"integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
"dev": true,
"license": "MIT",
"dependencies": {
"anymatch": "~3.1.2",
"braces": "~3.0.2",
"glob-parent": "~5.1.2",
"is-binary-path": "~2.1.0",
"is-glob": "~4.0.1",
"normalize-path": "~3.0.0",
"readdirp": "~3.6.0"
},
"engines": {
"node": ">= 8.10.0"
},
"funding": {
"url": "https://paulmillr.com/funding/"
},
"optionalDependencies": {
"fsevents": "~2.3.2"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true,
"license": "MIT"
},
"node_modules/debug": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/figlet": {
"version": "1.8.2",
"resolved": "https://registry.npmjs.org/figlet/-/figlet-1.8.2.tgz",
"integrity": "sha512-iPCpE9B/rOcjewIzDnagP9F2eySzGeHReX8WlrZQJkqFBk2wvq8gY0c6U6Hd2y9HnX1LQcYSeP7aEHoPt6sVKQ==",
"license": "MIT",
"bin": {
"figlet": "bin/index.js"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true,
"license": "MIT",
"dependencies": {
"to-regex-range": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/glob-parent": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
"integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
"dev": true,
"license": "ISC",
"dependencies": {
"is-glob": "^4.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
}
},
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
"integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==",
"dev": true,
"license": "ISC"
},
"node_modules/is-binary-path": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
"integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"binary-extensions": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-extglob": "^2.1.1"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/is-number": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"dev": true,
"license": "MIT"
},
"node_modules/nodemon": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.10.tgz",
"integrity": "sha512-WDjw3pJ0/0jMFmyNDp3gvY2YizjLmmOUQo6DEBY+JgdvW/yQ9mEeSw6H5ythl5Ny2ytb7f9C2nIbjSxMNzbJXw==",
"dev": true,
"license": "MIT",
"dependencies": {
"chokidar": "^3.5.2",
"debug": "^4",
"ignore-by-default": "^1.0.1",
"minimatch": "^3.1.2",
"pstree.remy": "^1.1.8",
"semver": "^7.5.3",
"simple-update-notifier": "^2.0.0",
"supports-color": "^5.5.0",
"touch": "^3.1.0",
"undefsafe": "^2.0.5"
},
"bin": {
"nodemon": "bin/nodemon.js"
},
"engines": {
"node": ">=10"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/nodemon"
}
},
"node_modules/normalize-path": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
"integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/picomatch": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8.6"
},
"funding": {
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/prettier": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.6.2.tgz",
"integrity": "sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/pstree.remy": {
"version": "1.1.8",
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
"integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==",
"dev": true,
"license": "MIT"
},
"node_modules/readdirp": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
"integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
"dev": true,
"license": "MIT",
"dependencies": {
"picomatch": "^2.2.1"
},
"engines": {
"node": ">=8.10.0"
}
},
"node_modules/semver": {
"version": "7.7.2",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz",
"integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==",
"dev": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/simple-update-notifier": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz",
"integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==",
"dev": true,
"license": "MIT",
"dependencies": {
"semver": "^7.5.3"
},
"engines": {
"node": ">=10"
}
},
"node_modules/sprintf-js": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
"license": "BSD-3-Clause"
},
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"license": "MIT",
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"is-number": "^7.0.0"
},
"engines": {
"node": ">=8.0"
}
},
"node_modules/touch": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz",
"integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==",
"dev": true,
"license": "ISC",
"bin": {
"nodetouch": "bin/nodetouch.js"
}
},
"node_modules/undefsafe": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
"integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==",
"dev": true,
"license": "MIT"
},
"node_modules/ws": {
"version": "8.18.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
"license": "MIT",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
} }
}
} }
}
} }

View File

@@ -18,6 +18,7 @@
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"figlet": "^1.8.2", "figlet": "^1.8.2",
"sprintf-js": "^1.1.3",
"ws": "^8.14.2" "ws": "^8.14.2"
}, },
"devDependencies": { "devDependencies": {
@@ -33,7 +34,6 @@
"quoteProps": "consistent", "quoteProps": "consistent",
"singleQuote": false, "singleQuote": false,
"trailingComma": "all", "trailingComma": "all",
"tabWidth": 4,
"bracketSpacing": true, "bracketSpacing": true,
"objectWrap": "preserve", "objectWrap": "preserve",
"arrowParens": "always" "arrowParens": "always"

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 448 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,11 +1,16 @@
import { crackdown } from "./crackdown.js"; import { crackdown } from "./crackdown.js";
import { parseArgs } from "./parseArgs.js";
import { MessageType } from "./messages.js";
const MsgContext.REPLY = "R"; /** Regex to validate if a :help [topic] command i entered correctly */
const QUIT = "QUIT";
const HELP = "HELP";
const COLON = ":";
const helpRegex = /^:help(?:\s+(.*))?$/; const helpRegex = /^:help(?:\s+(.*))?$/;
const colonRegex = /^:([a-z0-9_]+)(?:\s+(.*))?$/;
/** Regex to validate if a :<command> [args] was entered correctly */
const colonRegex = /^:([a-z0-9_]+)(?:\s+(.*?)\s*)?$/;
/**
* The client that talks to the MUD Sever
*/
class MUDClient { class MUDClient {
// //
// Constructor // Constructor
@@ -14,7 +19,7 @@ class MUDClient {
this.websocket = null; this.websocket = null;
/** @type {boolean} Are we in development mode (decided by the server); */ /** @type {boolean} Are we in development mode (decided by the server); */
this.dev = false; this.isDev = false;
this.promptOptions = {}; this.promptOptions = {};
this.shouldReply = false; this.shouldReply = false;
@@ -176,8 +181,8 @@ class MUDClient {
// //
// The quit command has its own message type // The quit command has its own message type
if (inputText === ":quit") { if (inputText === ":quit") {
this.send(QUIT); this.send(MessageType.QUIT);
this.writeToOutput("> " + inputText, { class: "input" }); this.writeToOutput("> " + inputText, { verbatim: true, class: "input" });
return; return;
} }
@@ -193,8 +198,8 @@ class MUDClient {
let help = helpRegex.exec(inputText); let help = helpRegex.exec(inputText);
if (help) { if (help) {
console.log("here"); console.log("here");
help[1] ? this.send(HELP, help[1].trim()) : this.send(HELP); help[1] ? this.send(MshType.HELP, help[1].trim()) : this.send(MshType.HELP);
this.writeToOutput("> " + inputText, { class: "input" }); this.writeToOutput("> " + inputText, { verbatim: true, class: "input" });
return; return;
} }
@@ -204,19 +209,14 @@ class MUDClient {
// _ | (_| (_) | | | | | | | | | | | (_| | | | | (_| | // _ | (_| (_) | | | | | | | | | | | (_| | | | | (_| |
// (_) \___\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|\__,_| // (_) \___\___/|_| |_| |_|_| |_| |_|\__,_|_| |_|\__,_|
//------------------------------------------------------ //------------------------------------------------------
let colonCommand = colonRegex.exec(inputText); const colon = colonRegex.exec(inputText);
if (colonCommand) { if (colon) {
this.send(COLON, colonCommand[1], colonCommand[2]); const args = typeof colon[2] === "string" ? parseArgs(colon[2]) : [];
this.writeToOutput("> " + inputText, { class: "input" }); this.send(MessageType.COLON, colon[1], args);
this.writeToOutput("> " + inputText, { verbatim: true, class: "input colon" });
return; return;
} }
//
// The server doesn't want any input from us, so we just ignore this input
if (!this.shouldReply) {
// the server is not ready for data!
return;
}
// _ // _
// _ __ ___ _ __ | |_ _ // _ __ ___ _ __ | |_ _
// | '__/ _ \ '_ \| | | | | // | '__/ _ \ '_ \| | | | |
@@ -227,6 +227,12 @@ class MUDClient {
// We handle replies below // We handle replies below
//------------------------- //-------------------------
//
if (!this.shouldReply) {
// the server is not ready for data!
return;
}
// The server wants a password, let's hash it before sending it. // The server wants a password, let's hash it before sending it.
if (this.promptOptions.password) { if (this.promptOptions.password) {
inputText = await this.hashPassword(inputText); inputText = await this.hashPassword(inputText);
@@ -238,14 +244,14 @@ class MUDClient {
this.username = inputText; this.username = inputText;
} }
this.send(REPLY, inputText); this.send(MessageType.REPLY, inputText);
this.shouldReply = false; this.shouldReply = false;
this.promptOptions = {}; this.promptOptions = {};
// //
// We add our own command to the output stream so the // We add our own command to the output stream so the
// player can see what they typed. // player can see what they typed.
this.writeToOutput("> " + inputText, { class: "input" }); this.writeToOutput("> " + inputText, { verbatim: true, class: "input" });
return; return;
} }
@@ -257,7 +263,7 @@ class MUDClient {
// //
/** @param {any[]} data*/ /** @param {any[]} data*/
onMessageReceived(data) { onMessageReceived(data) {
if (this.dev) { if (this.isDev) {
console.debug(data); console.debug(data);
} }
const messageType = data.shift(); const messageType = data.shift();
@@ -291,7 +297,7 @@ class MUDClient {
return this.handleDebugMessages(data); return this.handleDebugMessages(data);
} }
if (this.dev) { if (this.isDev) {
this.writeToOutput(`unknown message type: ${messageType}: ${JSON.stringify(data)}`, { this.writeToOutput(`unknown message type: ${messageType}: ${JSON.stringify(data)}`, {
class: "debug", class: "debug",
verbatim: true, verbatim: true,
@@ -314,7 +320,7 @@ class MUDClient {
// Debug messages let the server send data to be displayed on the player's screen // Debug messages let the server send data to be displayed on the player's screen
// and also logged to the players browser's log. // and also logged to the players browser's log.
handleDebugMessages(data) { handleDebugMessages(data) {
if (!this.dev) { if (!this.isDev) {
return; // debug messages are thrown away if we're not in dev mode. return; // debug messages are thrown away if we're not in dev mode.
} }
this.writeToOutput(data, { class: "debug", verbatim: true }); this.writeToOutput(data, { class: "debug", verbatim: true });
@@ -332,16 +338,16 @@ class MUDClient {
console.debug("Incoming system message", data); console.debug("Incoming system message", data);
/** @type {string} */ /** @type {string} */
const messageType = data.shift(); const systemMessageType = data.shift();
switch (messageType) { switch (systemMessageType) {
case "username": case "username":
this.username = data[0]; this.username = data[0];
break; break;
case "dev": case "dev":
// This is a message that tells us that the server is in // This is a message that tells us that the server is in
// "dev" mode, and that we should do the same. // "dev" mode, and that we should do the same.
this.dev = data[0]; this.isDev = !!data[0];
this.status.textContent = "[DEV] " + this.status.textContent; this.status.textContent = "[DEV] " + this.status.textContent;
break; break;
case "salt": case "salt":
@@ -349,12 +355,12 @@ class MUDClient {
console.debug("updating crypto salt", data[0]); console.debug("updating crypto salt", data[0]);
break; break;
default: default:
console.debug("unknown system message", messageType, data); console.debug("unknown system message", systemMessageType, data);
} }
// If we're in dev mode, we should output all system messages (in a shaded/faint fashion). // If we're in dev mode, we should output all system messages (in a shaded/faint fashion).
if (this.dev) { if (this.isDev) {
this.writeToOutput(`system message: ${messageType} = ${JSON.stringify(data)}`, { class: "debug" }); this.writeToOutput(`system message: ${systemMessageType} = ${JSON.stringify(data)}`, { class: "debug" });
} }
return; return;
} }
@@ -436,7 +442,7 @@ class MUDClient {
* @param {string} className * @param {string} className
*/ */
updateStatus(message, className) { updateStatus(message, className) {
this.status.textContent = this.dev ? `[DEV] Status: ${message}` : `Status: ${message}`; this.status.textContent = this.isDev ? `[DEV] Status: ${message}` : `Status: ${message}`;
this.status.className = className; this.status.className = className;
} }
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 783 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
server/public/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

1
server/public/messages.js Symbolic link
View File

@@ -0,0 +1 @@
../utils/messages.js

1
server/public/mustbe.js Symbolic link
View File

@@ -0,0 +1 @@
../utils/mustbe.js

View File

@@ -1,5 +1,3 @@
import { mustBeString } from "./mustbe.js";
/** /**
* Parse a command string into arguments. For use with colon-commands. * Parse a command string into arguments. For use with colon-commands.
* *
@@ -7,7 +5,9 @@ import { mustBeString } from "./mustbe.js";
* @returns {(string|number)[]} Command arguments * @returns {(string|number)[]} Command arguments
*/ */
export function parseArgs(cmdString) { export function parseArgs(cmdString) {
mustBeString(cmdString); if (typeof cmdString === "string") {
throw new Error("Expected string. GoT a finger in the eye instead");
}
const args = []; const args = [];
const quoteChars = ["'", '"', "`"]; const quoteChars = ["'", '"', "`"];
const backslash = "\\"; const backslash = "\\";
@@ -16,7 +16,8 @@ export function parseArgs(cmdString) {
let inQuotes = false; // are we inside quotes of some kind? let inQuotes = false; // are we inside quotes of some kind?
let currentQuoteChar = ""; // if were in quotes, which are they? let currentQuoteChar = ""; // if were in quotes, which are they?
const push = (value) => { // helper function
const pushVal = (value) => {
const n = Number(value); const n = Number(value);
if (Number.isSafeInteger(n)) { if (Number.isSafeInteger(n)) {
args.push(n); args.push(n);
@@ -39,7 +40,7 @@ export function parseArgs(cmdString) {
} else if (char === " " || char === "\t") { } else if (char === " " || char === "\t") {
// Whitespace - end current arg if it exists // Whitespace - end current arg if it exists
if (currentArg) { if (currentArg) {
push(currentArg); pushVal(currentArg);
currentArg = ""; currentArg = "";
} }
// Skip multiple whitespace // Skip multiple whitespace
@@ -68,7 +69,7 @@ export function parseArgs(cmdString) {
// Add final argument if exists // Add final argument if exists
if (currentArg) { if (currentArg) {
push(currentArg); pushVal(currentArg);
} }
if (currentQuoteChar) { if (currentQuoteChar) {
@@ -79,5 +80,3 @@ export function parseArgs(cmdString) {
return args; return args;
} }
console.log(parseArgs("\"k1m er '-9 ' `anus pikke`"));

View File

@@ -0,0 +1 @@
{"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}

View File

@@ -2,10 +2,10 @@ import { PasswordPrompt } from "./passwordPrompt.js";
import { Player } from "../../models/player.js"; import { Player } from "../../models/player.js";
import { Scene } from "../scene.js"; import { Scene } from "../scene.js";
import { UsernamePrompt } from "./usernamePrompt.js"; import { UsernamePrompt } from "./usernamePrompt.js";
import { CreateUsernamePrompt } from "../playerCreation/createUsernamePrompt.js";
/** @property {Session} session */ /** @property {Session} session */
export class AuthenticationScene extends Scene { export class AuthenticationScene extends Scene {
introText = [ introText = [
"= Welcome", // "= Welcome", //
]; ];
@@ -15,13 +15,13 @@ export class AuthenticationScene extends Scene {
onReady() { onReady() {
// current prompt // current prompt
this.doPrompt(new UsernamePrompt(this)); this.show(UsernamePrompt);
} }
/** @param {Player} player */ /** @param {Player} player */
usernameAccepted(player) { usernameAccepted(player) {
this.player = player; this.player = player;
this.doPrompt(new PasswordPrompt(this)); this.show(PasswordPrompt);
} }
passwordAccepted() { passwordAccepted() {
@@ -34,4 +34,8 @@ export class AuthenticationScene extends Scene {
this.session.setScene("new JustLoggedInScene"); this.session.setScene("new JustLoggedInScene");
} }
} }
createPlayer() {
scene.session.setScene(new PlayerCreationScene(this.scene));
}
} }

View File

@@ -1,7 +1,6 @@
import { Prompt } from "../prompt.js"; import { Prompt } from "../prompt.js";
import * as security from "../../utils/security.js"; import * as security from "../../utils/security.js";
import { Config } from "../../config.js"; import { Config } from "../../config.js";
import { context } from "../../utils/messages.js";
export class PasswordPrompt extends Prompt { export class PasswordPrompt extends Prompt {
// //

View File

@@ -1,11 +1,16 @@
import { Player } from "../../models/player.js"; import { Player } from "../../models/player.js";
import { Prompt } from "../prompt.js"; import { Prompt } from "../prompt.js";
import * as security from "../../utils/security.js"; import * as security from "../../utils/security.js";
import { context } from "../../utils/messages.js";
import { gGame } from "../../models/globals.js"; import { gGame } from "../../models/globals.js";
import { PlayerCreationScene } from "../playerCreation/playerCreationSene.js"; import { PlayerCreationScene } from "../playerCreation/playerCreationSene.js";
import { Config } from "../../config.js"; import { Config } from "../../config.js";
import { AuthenticationScene } from "./authenticationScene.js";
/**
* @class
*
* @property {AuthenticationScene} scene
*/
export class UsernamePrompt extends Prompt { export class UsernamePrompt extends Prompt {
// //
promptText = [ promptText = [
@@ -24,17 +29,14 @@ export class UsernamePrompt extends Prompt {
// Let the client know that we're asking for a username // Let the client know that we're asking for a username
promptOptions = { username: true }; promptOptions = { username: true };
// /** @returns {AuthenticationScene} */
// User entered ":create" get scene() {
onColon_create() { return this._scene;
// User creation scene.
this.scene.session.setScene(new PlayerCreationScene(this.scene));
} }
// //
// User replied to our prompt // User replied to our prompt
onReply(text) { onReply(text) {
// //
// do basic syntax checks on usernames // do basic syntax checks on usernames
if (!security.isUsernameSane(text)) { if (!security.isUsernameSane(text)) {

View File

@@ -1,7 +1,6 @@
import { Prompt } from "../prompt.js"; import { Prompt } from "../prompt.js";
import * as security from "../../utils/security.js"; import * as security from "../../utils/security.js";
import { Config } from "../../config.js"; import { Config } from "../../config.js";
import { context } from "../../utils/messages.js";
export class CreatePasswordPrompt extends Prompt { export class CreatePasswordPrompt extends Prompt {
// //

View File

@@ -19,7 +19,7 @@ export class PlayerCreationScene extends Scene {
this.session.calamity("Server is full, no more players can be created"); this.session.calamity("Server is full, no more players can be created");
} }
this.doPrompt(new CreateUsernamePrompt(this)); this.showPrompt(new CreateUsernamePrompt(this));
} }
/** /**
@@ -27,13 +27,13 @@ export class PlayerCreationScene extends Scene {
* *
* @param {string} username * @param {string} username
*/ */
onUsernameAccepted(username) { usernameAccepted(username) {
const player = gGame.createPlayer(username); const player = gGame.createPlayer(username);
this.player = player; this.player = player;
this.session.sendSystemMessage("salt", player.salt); this.session.sendSystemMessage("salt", player.salt);
this.session.sendText(`Username _*${username}*_ is available, and I've reserved it for you :)`); this.session.sendText(`Username _*${username}*_ is available, and I've reserved it for you :)`);
this.doPrompt("new passwordprompt"); this.showPrompt("new passwordprompt");
} }
/** /**
@@ -42,9 +42,15 @@ export class PlayerCreationScene extends Scene {
* *
* @param {string} password * @param {string} password
*/ */
onPasswordAccepted(password) { passwordAccepted(password) {
this.password = password; this.password = password;
this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry for that ;)"); this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry for that ;)");
this.player.setPasswordHash(security.generateHash(this.password)); this.player.setPasswordHash(security.generateHash(this.password));
} }
//
// User entered ":create"
onColon__create() {
this.scene.createPlayer();
}
} }

View File

@@ -1,10 +1,10 @@
import figlet from "figlet"; import figlet from "figlet";
import { gGame } from "../models/globals.js"; import { gGame } from "../models/globals.js";
import * as msg from "../utils/messages.js";
import { Session } from "../models/session.js"; import { Session } from "../models/session.js";
import { Scene } from "./scene.js"; import { Scene } from "./scene.js";
import { WebsocketMessage } from "../utils/messages.js"; import { MessageType, WebsocketMessage } from "../utils/messages.js";
import { mustBe, mustBeString } from "../utils/mustbe.js"; import { mustBe, mustBeString } from "../utils/mustbe.js";
import { sprintf } from "sprintf-js";
/** /**
* @typedef {object} PromptMethods * @typedef {object} PromptMethods
@@ -19,8 +19,13 @@ import { mustBe, mustBeString } from "../utils/mustbe.js";
* - onColon(...) * - onColon(...)
*/ */
export class Prompt { export class Prompt {
/** @protected @readonly @constant @type {Scene} */ /** @private @readonly @type {Scene} */
scene; _scene;
/** @type {Scene} */
get scene() {
return this._scene;
}
// //
// Extra info about the prompt we send to the client. // Extra info about the prompt we send to the client.
@@ -58,7 +63,7 @@ export class Prompt {
if (!(scene instanceof Scene)) { if (!(scene instanceof Scene)) {
throw new Error("Expected an instance of >>Scene<< but got " + typeof scene); throw new Error("Expected an instance of >>Scene<< but got " + typeof scene);
} }
this.scene = scene; this._scene = scene;
} }
/** /**
@@ -92,24 +97,42 @@ export class Prompt {
* Triggered when a user types a :command that begins with a colon * Triggered when a user types a :command that begins with a colon
* *
* @param {string} command * @param {string} command
* @param {string} argLine * @param {any[]} args
*/ */
onColon(command, argLine) {
const methodName = "onColon_" + command; onColon(command, args) {
const method = this[methodName]; const methodName = "onColon__" + command;
if (typeof method === "function") { const property = this[methodName];
method.call(this, argLine);
//
// Default: we have no handler for the Foo command,
// So let's see if daddy can handle it.
if (property === undefined) {
return this.scene.onColon(command, args);
}
//
// If the prompt has a method called onColon_foo() =>
if (typeof property === "function") {
property.call(this, args);
return; return;
} }
// //
// For static "denial of commands" such as :inv ==> "you cannot access your inventory right now" // If the prompt has a _string_ called onColon_foo =>
if (typeof method === "string") { if (typeof property === "string") {
this.sendText(method); this.sendText(property);
return;
} }
// :inv ==> you cannot INV right now //
this.sendError(`You cannot ${command.toUpperCase()} right now`); // We found a property that has the right name but the wrong type.
throw new Error(
[
`Logic error. Prompt has a handler for a command called ${command}`,
`but it is neither a function or a string, but a ${typeof property}`,
].join(" "),
);
} }
/** /**
@@ -167,7 +190,7 @@ export class Prompt {
} }
/** /**
* @param {string} systemMessageType * @param {string} systemMessageType The subtype of the system message (dev, salt, username, etc.)
* @param {any?} value * @param {any?} value
*/ */
sendSystemMessage(...args) { sendSystemMessage(...args) {
@@ -184,13 +207,6 @@ export class Prompt {
// //
// Easter ægg // Easter ægg
onColon_pull_out_wand = "You cannot pull out your wand right now! But thanks for trying 😘🍌🍆"; // Example of having a string as a colon-handler
onColon__pull_out_wand = "You cannot pull out your wand right now! But thanks for trying 😘🍌🍆";
//
// Easter ægg2
onColon_imperial(argLine) {
const n = Number(argLine);
this.sendText(`${n} centimeters is only ${n / 2.54} inches. This is why americans have such small wands`);
}
} }

View File

@@ -1,11 +1,16 @@
import { Session } from "../models/session.js"; import { Session } from "../models/session.js";
import { Prompt } from "./prompt.js"; import { Prompt } from "./prompt.js";
const MsgContext.ERROR_INSANE_PASSWORD = "Invalid password.";
const MsgContext.ERROR_INCORRECT_PASSWOD = "Incorrect password.";
/** /**
* Scene - a class for showing one or more prompts in a row.
*
* Scenes are mostly there to keep track of which prompt to show,
* and to store data for subsequent prompts to access.
*
* The prompts themselves are responsible for data validation and
* interpretation.
*
* @abstract * @abstract
* @method onReady
*/ */
export class Scene { export class Scene {
/** /**
@@ -31,8 +36,7 @@ export class Scene {
return this._prompt; return this._prompt;
} }
constructor() { constructor() {}
}
/** @param {Session} session */ /** @param {Session} session */
execute(session) { execute(session) {
@@ -51,8 +55,73 @@ export class Scene {
/** /**
* @param {Prompt} prompt * @param {Prompt} prompt
*/ */
doPrompt(prompt) { showPrompt(prompt) {
this._prompt = prompt; this._prompt = prompt;
prompt.execute(); prompt.execute();
} }
/** @param {new (scene: Scene) => Prompt} promptClassReference */
show(promptClassReference) {
this.showPrompt(new promptClassReference(this));
}
/**
* Triggered when a user types a :command that begins with a colon
* and the current Prompt cannot handle that command.
*
* @param {string} command
* @param {any[]} args
*/
onColon(command, args) {
const propertyName = "onColon__" + command;
const property = this[propertyName];
//
// Default: we have no handler for the Foo command
if (property === undefined) {
this.session.sendError(`You cannot ${command.toUpperCase()} right now`, { verbatim: true }); // :foo ==> you cannot FOO right now
return;
}
//
// If the prompt has a method called onColon_foo() =>
if (typeof property === "function") {
property.call(this, args);
return;
}
//
// If the prompt has a _string_ called onColon_foo =>
if (typeof property === "string") {
this.session.sendText(property);
return;
}
//
// We found a property that has the right name but the wrong type.
throw new Error(
[
`Logic error. Scene has a handler for a command called ${command}`,
`but it is neither a function or a string, but a ${typeof property}`,
].join(" "),
);
}
//
// Easter ægg
// Example dynamic colon handler
/** @param {any[]} args */
onColon__imperial(args) {
if (args.length === 0) {
this.session.sendText("The imperial system is the freeest system ever. Also the least good");
}
const n = Number(args[0]);
this.session.sendText(
sprintf("%.2f centimeters is only %.2f inches. This is american wands are so short!", n, n / 2.54),
);
}
onColon__hi = "Hoe";
} }

View File

@@ -2,12 +2,12 @@ import WebSocket, { WebSocketServer } from "ws";
import http from "http"; import http from "http";
import path from "path"; import path from "path";
import fs from "fs"; import fs from "fs";
import * as msg from "./utils/messages.js";
import { Session } from "./models/session.js"; import { Session } from "./models/session.js";
import { GameSeeder } from "./seeders/gameSeeder.js"; import { GameSeeder } from "./seeders/gameSeeder.js";
import { Config } from "./config.js"; import { Config } from "./config.js";
import { gGame } from "./models/globals.js"; import { gGame } from "./models/globals.js";
import { AuthenticationScene } from "./scenes/authentication/authenticationScene.js"; import { AuthenticationScene } from "./scenes/authentication/authenticationScene.js";
import { MessageType, WebsocketMessage, formatMessage } from "./utils/messages.js";
// __ __ _ _ ____ ____ // __ __ _ _ ____ ____
// | \/ | | | | _ \ / ___| ___ _ ____ _____ _ __ // | \/ | | | | _ \ / ___| ___ _ ____ _____ _ __
@@ -32,7 +32,7 @@ class MudServer {
console.info("New connection established"); console.info("New connection established");
const session = new Session(websocket, gGame); const session = new Session(websocket, gGame);
if (Config.dev) { if (Config.dev) {
websocket.send(msg.prepareToSend(msg.SYSTEM, "dev", true)); websocket.send(formatMessage(MessageType.SYSTEM, "dev", true));
} }
// ____ _ ___ ____ _____ // ____ _ ___ ____ _____
@@ -56,7 +56,7 @@ class MudServer {
this.onMessage(session, data); this.onMessage(session, data);
} catch (error) { } catch (error) {
console.error(error, data.toString(), data); console.error(error, data.toString(), data);
websocket.send(msg.prepareToSend(msg.CALAMITY, error)); websocket.send(formatMessage(MessageType.CALAMITY, error));
session.close(); session.close();
} }
}); });
@@ -159,7 +159,7 @@ class MudServer {
return; return;
} }
const msgObj = new msg.WebsocketMessage(data.toString()); const msgObj = new WebsocketMessage(data.toString());
// //
// Handle replies to prompts. The main workhorse of the game. // Handle replies to prompts. The main workhorse of the game.
@@ -174,7 +174,7 @@ class MudServer {
} }
// //
// Handle QUIT messages. When the player types :quit // Handle MessageType.QUIT messages. When the player types :quit
if (msgObj.isQuit()) { if (msgObj.isQuit()) {
session.scene.onQuit(); session.scene.onQuit();
session.close(0, "Closing the socket, graceful goodbye!"); session.close(0, "Closing the socket, graceful goodbye!");
@@ -184,7 +184,7 @@ class MudServer {
// //
// Handle any text that starts with ":" that isn't :help or :quit // Handle any text that starts with ":" that isn't :help or :quit
if (msgObj.isColon()) { if (msgObj.isColon()) {
return session.scene.prompt.onColon(msgObj.command, msgObj.argLine); return session.scene.prompt.onColon(msgObj.command, msgObj.args);
} }
// //

View File

@@ -1,19 +1,6 @@
import { mustBe, mustBeInteger, mustBeString, mustMatch } from "./mustbe.js"; import { mustBe, mustBeString, mustMatch } from "./mustbe.js";
const colonCommandRegex = /^:([a-z0-9_]+)(:?\s*(.*))?$/; export const MessageType = Object.freeze({
/**
* Enum-like object holding placeholder tokens.
*
* @readonly
* @enum {string}
*/
export const MsgContext = Object.freeze({
PASSWORD: ":password",
USERNAME: ":username",
});
export const MsgTtype = Object.freeze({
/** /**
* Very bad logic error. Player must quit game, refresh page, and log in again. * Very bad logic error. Player must quit game, refresh page, and log in again.
* *
@@ -28,7 +15,7 @@ export const MsgTtype = Object.freeze({
* *
* Server-->Client-->Player * Server-->Client-->Player
*/ */
MsgContext.ERROR: "E", ERROR: "E",
/** /**
* Message to be displayed. * Message to be displayed.
@@ -42,7 +29,7 @@ export const MsgTtype = Object.freeze({
* *
* Player-->Client-->Server * Player-->Client-->Server
*/ */
MsgContext.REPLY: "R", REPLY: "R",
/** /**
* Player wants to quit. * Player wants to quit.
@@ -93,7 +80,7 @@ export const MsgTtype = Object.freeze({
* Represents a message sent to/from client * Represents a message sent to/from client
* *
* @property {string?} command * @property {string?} command
* @property {string?} argLine * @property {any[]} args
*/ */
export class WebsocketMessage { export class WebsocketMessage {
/** @protected @type {any[]} _arr The array that contains the message data */ /** @protected @type {any[]} _arr The array that contains the message data */
@@ -136,22 +123,22 @@ export class WebsocketMessage {
this.type = mustBeString(data[0]); this.type = mustBeString(data[0]);
switch (this.type) { switch (this.type) {
case MsgContext.REPLY: // player ==> client ==> server case MessageType.REPLY: // player ==> client ==> server
this.text = mustBeString(data[1]); this.text = mustBeString(data[1]);
break; break;
case HELP: // player ==> client ==> server case MessageType.HELP: // player ==> client ==> server
this.text = data[1] === undefined ? "" : mustBeString(data[1]).trim(); this.text = data[1] === undefined ? "" : mustBeString(data[1]).trim();
break; break;
case COLON: // player ==> client ==> server case MessageType.COLON: // player ==> client ==> server
this.command = mustMatch(data[1], /^[a-z0-9_]+$/); this.command = mustMatch(data[1], /^[a-z0-9_]+$/);
this.argLine = data[2]; // parse?? this.args = mustBe(data[2], "any[]");
break; break;
case DEBUG: // server ==> client case MessageType.DEBUG: // server ==> client
case MsgContext.ERROR: // server ==> client ==> player case MessageType.ERROR: // server ==> client ==> player
case QUIT: // player ==> client ==> server case MessageType.QUIT: // player ==> client ==> server
case SYSTEM: // client <==> server case MessageType.SYSTEM: // client <==> server
case PROMPT: // server ==> client ==> player case MessageType.PROMPT: // server ==> client ==> player
case TEXT: // server ==> client ==> player case MessageType.TEXT: // server ==> client ==> player
break; break;
default: default:
throw new Error(`Unknown message type: >>${typeof this.type}<<`); throw new Error(`Unknown message type: >>${typeof this.type}<<`);
@@ -159,27 +146,27 @@ export class WebsocketMessage {
} }
isQuit() { isQuit() {
return this.type === QUIT; return this.type === MessageType.QUIT;
} }
isHelp() { isHelp() {
return this.type === HELP; return this.type === MessageType.HELP;
} }
isColon() { isColon() {
return this.type === COLON; return this.type === MessageType.COLON;
} }
isReply() { isReply() {
return this.type === MsgContext.REPLY; return this.type === MessageType.REPLY;
} }
isSysMessage() { isSysMessage() {
return this.type === SYSTEM; return this.type === MessageType.SYSTEM;
} }
isDebug() { isDebug() {
return this.type === DEBUG; return this.type === MessageType.DEBUG;
} }
} }
@@ -189,6 +176,6 @@ export class WebsocketMessage {
* @param {string} messageType * @param {string} messageType
* @param {...any} args * @param {...any} args
*/ */
export function prepareToSend(messageType, ...args) { export function formatMessage(messageType, ...args) {
return JSON.stringify([messageType, ...args]); return JSON.stringify([messageType, ...args]);
} }

View File

@@ -12,8 +12,14 @@ export function mustBe(value, ...types) {
return value; return value;
} }
const isArray = Array.isArray(value);
if (isArray && (types.includes("any[]") || types.includes("array"))) {
return value;
}
// NOTE: only checks first element of array if it's a string. // NOTE: only checks first element of array if it's a string.
if (types.includes("strings[]") && Array.isArray(value) && (value.length === 0 || typeof value[0] === "string")) { if (isArray && types.includes("strings[]") && (value.length === 0 || typeof value[0] === "string")) {
return value; return value;
} }
@@ -24,12 +30,6 @@ export function mustBeString(value) {
return mustBe(value, "string"); return mustBe(value, "string");
} }
export function mustBeInteger(value) {
if (typeof value === "number" && Number.isSafeInteger(value)) {
return value;
}
}
/** /**
* *
* @param {string} str * @param {string} str

View File

@@ -222,7 +222,7 @@ export function frameText(text, options) {
let output = ""; let output = "";
// //
// GENERATE THE MARGIN SPACE ABOVE THE FRAMED TEXT // GENERATE THE MARGIN SPACE ABOVE THE FRAMED MsgType.TEXT
// //
// ( we insert space characters even though ) // ( we insert space characters even though )
// ( they wouldn't normally be visible. But ) // ( they wouldn't normally be visible. But )
@@ -263,7 +263,7 @@ export function frameText(text, options) {
).repeat(options.vPadding); ).repeat(options.vPadding);
// //
// GENERATE FRAMED TEXT SEGMENT // GENERATE FRAMED MsgType.TEXT SEGMENT
// //
// ║ My pretty ║ // ║ My pretty ║
// ║ text here ║ // ║ text here ║
@@ -318,7 +318,7 @@ export function frameText(text, options) {
"\n"; "\n";
// //
// GENERATE THE MARGIN SPACE BELOW THE FRAMED TEXT // GENERATE THE MARGIN SPACE BELOW THE FRAMED MsgType.TEXT
// //
// ( we insert space characters even though ) // ( we insert space characters even though )
// ( they wouldn't normally be visible. But ) // ( they wouldn't normally be visible. But )