diff --git a/bedhack.txt b/bedhack.txt new file mode 100755 index 0000000..9e2997b --- /dev/null +++ b/bedhack.txt @@ -0,0 +1,48 @@ +┌────────────────────────────────────────────────────────────────────────────────┐┌────────────────────────────────────────────────────────────────────────────┐ +│There are several objects here. ││Weapons │ +│Really attack Annootok? (yes|no) no ││k) 4 blessed daggers │ +│What do you want to use or apply? [dimPR or ?*] ││M) 4 blessed daggers (at the ready) │ +│V - a blessed scroll labeled ASHPD SODALG. ││l) 2 uncursed elven daggers │ +│j - 2 uncursed scrolls labeled DAIYEN FOOELS. ││a) blessed rustproof +1 Excalibur (weapon in hand) │ +│D - an uncursed scroll labeled ELAM EBOW. ││Armor │ +│W - 2 cursed scrolls labeled GNIK SISI VLE. ││O) blessed +1 orcish helm (being worn) │ +│s - 2 uncursed scrolls labeled HACKEM MUCHE. ││L) blessed rusty +1 pair of gauntlets of power (being worn) │ +│p - an uncursed scroll labeled KIRJE. u - an uncursed scroll labeled TEMOV. ││y) uncursed +0 pair of iron shoes (being worn) │ +│Q - 2 uncursed scrolls labeled VELOX NEB. ││c) uncursed +3 small shield (being worn) │ +│X - a blessed scroll labeled VERR YED HORRE. ││C) uncursed +0 leather cloak (being worn) │ +│What do you want to drop? [a-npqsuyzC-ELMO-RV-X or ?*] ││q) uncursed mummy wrapping │ +│You drop a blessed scroll labeled ASHPD SODALG. ││f) uncursed +0 ring mail (being worn) │ +│Annootok offers 30 gold pieces for your scroll labeled ASHPD SODALG. Sell it? ││Comestibles │ +│[ynaq] (y) ││n) uncursed food ration │ +│V - a blessed scroll labeled ASHPD SODALG. ││z) 2 uncursed pancakes │ +│What do you want to call? [dgijmpsuyDELO-RV-X or ?*] ││h) uncursed sprig of wolfsbane │ +│Call a scroll labeled ASHPD SODALG: ASHPD enchARM|rmvCUR|enchWEP ││b) 2 uncursed eggs │ +└────────────────────────────────────────────────────────────────────────────────┘│e) 2 uncursed lichen corpses │ +┌────────────────────────────────────────────────────────────────────────────────┐│Scrolls │ +│ ││j) 2 uncursed scrolls labeled DAIYEN FOOELS │ +│ ����������Ŀ ││PD) uncursed scroll labeled ELAM EBOW │ +│ �����Ŀ ��������������������������������*[���[[��������������������� ││TW) 2 cursed scrolls labeled GNIK SISI VLE │ +│ �����>���[��)�� ���������������Ŀ��������������Ŀ ����������� ││ms) 2 uncursed scrolls labeled HACKEM MUCHE │ +│ �����[� ���������� ������������������ �������������� ��������������� ││dp) uncursed scroll labeled KIRJE │ +│ � ����� ���������������������� �������������� � ���<������� ││Uu) uncursed scroll labeled TEMOV │ +│ ��������������������������������������� ����������� ││GQ) 2 uncursed scrolls labeled VELOX NEB │ +│ ���������������������������������� ������������ ││X) blessed scroll labeled VERR YED HORRE │ +│ ��������Ŀ������������������Ŀ����� ││V) blessed scroll called ASHPD enchARM|rmvCUR|enchWEP │ +│ ���������������������������� ����� ││Wands │ +│ ��������������������������!!%����� ││R) uncursed wand called slow monster │ +│ �����������������������_��%(%����� ││P) uncursed wand of digging │ +│ ��������������������������%%%����� ││Tools │ +│ �������Ŀ�������Ŀ����������Ĵ�[��[� ││m) uncursed bag called sack containing 2 items │ +│ ����%@)/�����((((�%� �(��������������� �������Ŀ ││d) uncursed lamp │ +│ *���������Ŀ ��B��?*)�����((]���� �(((����(� � ��������� ││Gems/Stones │ +│ *��[�������������������@����������+ � �(((��������� ����������� ││g) uncursed green gem named 3449 │ +│ �*���������� ���������������������������������� ��������� ││E) uncursed violet gem named COLDR ESP FIRER │ +│ 0����������� ���������������������������������� ��������� ││i) uncursed stone called gray not load │ +│ ������������ ���������������������������������� ││ │ +│ ││ │ +└────────────────────────────────────────────────────────────────────────────────┘│ │ +┌────────────────────────────────────────────────────────────────────────────────┐│ │ +│[M0ll13 the Fighter ] St:25 Dx:16 Co:18 In:8 Wi:12 Ch:7 ││ │ +│Lawful $:0 HP:63(63) Pw:26(26) AC:-3 Xp:8 ││ │ +│Dlvl:6 T:10818 ││ │ +└────────────────────────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────────────────┘ \ No newline at end of file diff --git a/server/.gitignore b/server/.gitignore old mode 100644 new mode 100755 diff --git a/server/diller.sqlite b/server/diller.sqlite new file mode 100755 index 0000000..2f15a37 Binary files /dev/null and b/server/diller.sqlite differ diff --git a/server/models/character.js b/server/models/character.js new file mode 100755 index 0000000..2049965 --- /dev/null +++ b/server/models/character.js @@ -0,0 +1,222 @@ +import * as roll from "../utils/dice.js"; +import * as id from "../utils/id.js"; + +/** + * A playable character. + * + * @class + */ +export class Character { + + /** @type {string} character's name */ + name; + + /** + * Alive? + * + * @protected + * @type {boolean} + */ + _alive = true; + get alive() { + return _alive; + } + + /** + * @protected + * @type {number} The number of XP the character has. + */ + _xp = 0; + get xp() { + return this._xp; + } + + /** + * @protected + * @type {number} The character's level. + */ + _level = 1; + get level() { + return this._level; + } + + /** + * @protected + * @type {string} unique name used for chats when there's a name clash and also other things that require a unique character id + */ + _id; + get id() { + return this._id; + } + + /** + * @protected + * @type {string} username of the player that owns this character. + */ + _username; + get username() { + return this._username; + } + + /** @type {string} Bloodline background */ + ancestry; + + /** @type {string} Foundational background */ + foundation; + + /** @type {string} Money */ + silver; + + /** @type {number} Current number of hit points */ + currentHitPoints; + + /** @type {number} Number of hit points when fully healed */ + maxHitPoints; + + /** @type {number} Number items you can carry */ + itemSlots; + + /** @type {Set} Things the character is particularly proficient at. */ + proficiencies = new Set(); + + /** @type {Map} List of all item templates in the game */ + _itemTemplates = new Map(); + + /** @type {Map} The list of locations in the game */ + _locations = new Map(); + + /** + * The characters in the game. + * + * @protected + * @type {Map} + */ + _characters = new Map(); + + /** + * @protected + * @type {Map} The list of users in the game + */ + _players = new Map(); +} diff --git a/server/models/item.js b/server/models/item.js new file mode 100755 index 0000000..090868f --- /dev/null +++ b/server/models/item.js @@ -0,0 +1,125 @@ +import { cleanIdentifier } from "../utils/helpers"; + +/** + * Item templates are the built-in basic items of the game. + * A character cannot directly own one of these items, + * they can only own CharacterItems, and ItemTemplates can be used to + * generate these CharacterItems. + */ +export class ItemTemplate { + + _id; + _name; + _description; + _itemSlots; + + /** @type {string} Item's machine-friendly name */ + get id() { + return this._id; + } + + /** @type {string} Item's human-friendly name */ + get name() { + return this._name; + } + + /** @type {string} Item's Description */ + get description() { + return this._description; + } + + /** @type {number} Number of Item Slots taken up by this item. */ + get itemSlots() { + return this._itemSlots; + } + + /** + * Constructor + * + * @param {string} name. The Item's Name. + * @param {number} itemSlots number of item slots the item takes up in a character's inventory. + * @param {string} description Item's detailed description. + * @param {string=} id Item's machine-friendly name. + */ + constructor(name, itemSlots, description, id) { + + if (typeof name !== "string") { + throw new Error("Name must be a string, but " + typeof name + " given."); + } + if (typeof description === "undefined") { + description = ""; + } + if (typeof description !== "string") { + throw new Error("Name must be a string, but " + typeof name + " given."); + } + if (!Number.isFinite(itemSlots)) { + throw new Error("itemSlots must be a finite number!"); + } + if (typeof id === "undefined") { + id = cleanIdentifier(name); + } + if (typeof id !== "string") { + throw new Error("id must be a string!"); + } + + this._name = name; + this._id = id; + this._itemSlots = Number(itemSlots); + this._description = ""; + + } + + createItem() { + return new ChracterItem(this._id, this._name, this._description, this._itemSlots); + } +} + +/** + * Characters can only own CharacterItems. + * + * If two characters have a short sword, each character has a CharacterItem + * with the name of Shortsword and with the same properties as the orignial Shortsword ItemTemplate. + * + * If a character picks up a Pickaxe in the dungeon, a new CharacterItem is spawned and injected into + * the character's Equipment Map. If the item is dropped/destroyed/sold, the CharacterItem is removed from + * the character's Equipment Map, and then deleted from memory. + * + * If a ChracterItem is traded away to another character, The other character inserts a clone of this item + * into their equipment map, and the item is then deleted from the previous owner's equipment list. + * This is done so we do not have mulltiple characters with pointers to the same item - we would rather risk + * dupes than wonky references. + * + * An added bonus is that the character can alter the name and description of the item. + * + * Another bonus is, that the game can spawn custom items that arent even in the ItemTemplate Set. + */ +export class CharacterItem { + + /** @type {string?} The unique name if the ItemTemplate this item is based on. May be null. */ + templateItemId; // We use the id instead of a pointer, could make garbage collection better. + + /** @type {string} The player's name for this item. */ + name; + + /** @type {string} The player's description for this item. */ + description; + + /** @type {number} Number of item slots taken up by this item. */ + itemSlots; + + constructor(templateItemId, name, description, itemSlots) { + this.templateItemId = templateItemId; + this.name = name; + this.description = description; + this.itemSlots = itemSlots; + } +} + +const i = new ItemTemplate("knife", 10000); + +const ci = new CharacterItem(); +console.log(ci); + + + + diff --git a/server/models/location.js b/server/models/location.js new file mode 100644 index 0000000..777d380 --- /dev/null +++ b/server/models/location.js @@ -0,0 +1,45 @@ +import { Portal } from "./portal"; + +/** +* Location in the world. +* +* Can contain characters, quests, monsters, loot, NPCs and more. +* +* Can contain mundane portals (such as doors or pathways) to adjacent rooms/locations, +* or magical portals to distant locations. +*/ +export class Location { + /** @protected @type string */ + _id; + get id() { + return this._id; + } + + /** @protected @type string */ + _name; + get name() { + return this._name; + } + + /** @protected @type string */ + _description; + get description() { + return this._description; + } + + /** @protected @type {Map} */ + _portals = new Map(); + get portals() { + return this._portals; + } + + /** + */ + constructor(id, name, description) { + this._id = id; + this._name = name; + this._description = description; + } +} + +const l = new Location("foo", "bar", "baz"); diff --git a/server/models/player.js b/server/models/player.js new file mode 100644 index 0000000..602a31e --- /dev/null +++ b/server/models/player.js @@ -0,0 +1,5 @@ +export class Player{ + _username; + _passwordHash; + alias; +} diff --git a/server/models/portal.js b/server/models/portal.js new file mode 100644 index 0000000..df02a09 --- /dev/null +++ b/server/models/portal.js @@ -0,0 +1,27 @@ +/** + * Connects two location ONE WAY. + * + * Example: two adjacent rooms connected by a door: + * Room A has a portal to Room B, and + * Room B has a portal to Room A. + * + * @todo Add encounters to portals + */ +export class Portal { + + /** + * Target Location. + */ + _targetLocationId; + + /** + * Description shown to the player when they inspect the portal from the source location. + */ + _description; + + /** + * Description shown to the player when they traverse the portal. + */ + _traversalDescription; + +} diff --git a/server/objects.adoc b/server/objects.adoc new file mode 100755 index 0000000..f17cd09 --- /dev/null +++ b/server/objects.adoc @@ -0,0 +1,51 @@ += The world + +== Character + +* Resides in a <>. +* Can move from one <> to another by using <> +* Has properties like + ** Name, Race + ** Money, XP, Level + ** Strength Dex, Etc. +* Can <> stuff. +* Can talk (chat) with other characters in the same <>. + +== Location +* Has a name +* Has a default description that is read when the location is entered. +* Can be almost any size. Typically, a Location is a room, but it can + be a clearing in a forest, the inside of a Dragon's guts, +* Has a map of of outbound <> + ** `key`: string, typically `"north"`, `"east"`, etc., but + it can also be more interesting such as `"trap door"`. + ** `value`: A <>. +* Does not need know about inbound portals. +* Can contain (or generate) Encounters. + + +== Portal +* A *One Way* transport to a given <>. +* Description (optional) that will be displayed when it is examined + (For instance, if it's a door, this description describes how the door + looks). +* SourceEncounter (optional) Can be guarded by an encounter that triggers only + when you attempt to approach the portal. The encounter can be a simple lock, + a puzzle, or a guardian. +* ArrivalEncounter (optional) Can trigger an encounter that occurs as soon as you + enter the target <>. +* ArrivalDescription (optional) A text that overrides the default text that you + would normally see upon entering a <>. If the player executes a + `look` command, the normal description will be shown. +* TargetSight (bool,false) Allows you to `look` into the target <>, + getting an idea of what you are about to get into. +* A door between two rooms is typically represented via two <>; + one one each direction. + +== Encounter + +* Can be one (or more) of: + ** Roleplay Encounter (NPC) + ** Combat Encounter (monsters) + ** Environmental Encounter (Trap, Puzzle) + ** Loot diff --git a/server/package-lock.json b/server/package-lock.json old mode 100644 new mode 100755 diff --git a/server/package.json b/server/package.json old mode 100644 new mode 100755 index f346fbd..456a1c7 --- a/server/package.json +++ b/server/package.json @@ -1,22 +1,23 @@ { - "name": "websocket-mud", - "version": "1.0.0", - "description": "A Multi-User Dungeon game using WebSockets and Node.js", - "main": "server.js", - "scripts": { - "start": "node server.js", - "dev": "nodemon server.js" - }, - "keywords": ["mud", "websocket", "game", "multiplayer"], - "author": "Your Name", - "license": "MIT", - "dependencies": { - "ws": "^8.14.2" - }, - "devDependencies": { - "nodemon": "^3.0.1" - }, - "engines": { - "node": ">=14.0.0" - } + "name": "websocket-mud", + "version": "1.0.0", + "description": "A Multi-User Dungeon game using WebSockets and Node.js", + "main": "server.js", + "type": "module", + "scripts": { + "start": "node server.js", + "dev": "nodemon server.js" + }, + "keywords": ["mud", "websocket", "game", "multiplayer"], + "author": "Your Name", + "license": "MIT", + "dependencies": { + "ws": "^8.14.2" + }, + "devDependencies": { + "nodemon": "^3.0.1" + }, + "engines": { + "node": ">=14.0.0" + } } diff --git a/server/server.js b/server/server.js index b3dd078..27bf457 100755 --- a/server/server.js +++ b/server/server.js @@ -5,7 +5,6 @@ const fs = require('fs'); /** * Player - * * @property WebSocket websocket */ class Player { diff --git a/server/utils/dice.js b/server/utils/dice.js new file mode 100644 index 0000000..33f16a9 --- /dev/null +++ b/server/utils/dice.js @@ -0,0 +1,13 @@ +export function withSides(sides) { + const r = Math.random() + return Math.floor(r * sides) + 1; +} + +export function d6() { + return withSides(6); +} + +export function d8() { + return withSides(8); +} + diff --git a/server/utils/helpers.js b/server/utils/helpers.js new file mode 100755 index 0000000..0e23a37 --- /dev/null +++ b/server/utils/helpers.js @@ -0,0 +1,12 @@ +export function rollDice(sides) { + const r = Math.random() + return Math.floor(r * sides) + 1; +} + +export function d6() { + return rollDice(6); +} + +export function d8() { + return rollDice(8); +} diff --git a/server/utils/id.js b/server/utils/id.js new file mode 100644 index 0000000..7ec1b28 --- /dev/null +++ b/server/utils/id.js @@ -0,0 +1,26 @@ +export function cleanName(s) { + if (typeof s !== "string") { + throw new Error("String expected, but got a ", typeof s); + } + return s.toLowerCase().replace(" ", "_").replace(/[^a-zA-Z0-9_]/, "_"); +} + +/** + * Generate a random number, convert it to base36, and return it as a string with 7-8 characters. + */ +export function miniUid() { + // we use 12 digits, but we could go up to 16 + return Number(Math.random().toFixed(12).substring(2)).toString(36); +} + +/** + * Generate an id from a name + */ +export function fromName(...names) { + let res = ""; + for (const name of names) { + res += ":" + cleanName(name); + } + + return res + ":" + miniUid(); +}