fjas
This commit is contained in:
48
bedhack.txt
Executable file
48
bedhack.txt
Executable file
@@ -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 │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ ││PD) uncursed scroll labeled ELAM EBOW │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD>Ŀ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>*[<5B><><EFBFBD>[[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││TW) 2 cursed scrolls labeled GNIK SISI VLE │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD>><3E><><EFBFBD>[<5B><>)<29><> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││ms) 2 uncursed scrolls labeled HACKEM MUCHE │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD>[<5B> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││dp) uncursed scroll labeled KIRJE │
|
||||||
|
│ <20> <20><><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20> <20><><EFBFBD><<3C><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││Uu) uncursed scroll labeled TEMOV │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││GQ) 2 uncursed scrolls labeled VELOX NEB │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││X) blessed scroll labeled VERR YED HORRE │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD> ││V) blessed scroll called ASHPD enchARM|rmvCUR|enchWEP │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD> ││Wands │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>!!%<25><><EFBFBD><EFBFBD><EFBFBD> ││R) uncursed wand called slow monster │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>_<EFBFBD><5F>%(%<25><><EFBFBD><EFBFBD><EFBFBD> ││P) uncursed wand of digging │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>%%%<25><><EFBFBD><EFBFBD><EFBFBD> ││Tools │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ<EFBFBD><C4BF><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ĵ<EFBFBD>[<5B><>[<5B> ││m) uncursed bag called sack containing 2 items │
|
||||||
|
│ <20><><EFBFBD><EFBFBD>%@)/<2F><><EFBFBD><EFBFBD><EFBFBD>((((<28>%<25> <20>(<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ ││d) uncursed lamp │
|
||||||
|
│ *<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ŀ <20><>B<EFBFBD><42>?*)<29><><EFBFBD><EFBFBD><EFBFBD>((]<5D><><EFBFBD><EFBFBD> <20>(((<28><><EFBFBD><EFBFBD>(<28> <20> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││Gems/Stones │
|
||||||
|
│ *<2A><>[<5B><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>@<40><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>+ <20> <20>(((<28><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││g) uncursed green gem named 3449 │
|
||||||
|
│ <20>*<2A><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││E) uncursed violet gem named COLDR ESP FIRER │
|
||||||
|
│ 0<><30><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││i) uncursed stone called gray not load │
|
||||||
|
│ <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD> ││ │
|
||||||
|
│ ││ │
|
||||||
|
└────────────────────────────────────────────────────────────────────────────────┘│ │
|
||||||
|
┌────────────────────────────────────────────────────────────────────────────────┐│ │
|
||||||
|
│[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 ││ │
|
||||||
|
└────────────────────────────────────────────────────────────────────────────────┘└────────────────────────────────────────────────────────────────────────────┘
|
||||||
0
server/.gitignore
vendored
Normal file → Executable file
0
server/.gitignore
vendored
Normal file → Executable file
BIN
server/diller.sqlite
Executable file
BIN
server/diller.sqlite
Executable file
Binary file not shown.
222
server/models/character.js
Executable file
222
server/models/character.js
Executable file
@@ -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<string>} Things the character is particularly proficient at. */
|
||||||
|
proficiencies = new Set();
|
||||||
|
|
||||||
|
/** @type {Map<string,number} Things the character is particularly proficient at. */
|
||||||
|
equipment = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {string} playerUname The name of player who owns this character. Note that the game can own a character - somehow.
|
||||||
|
* @param {string} name The name of the character
|
||||||
|
* @param {boolean} initialize Should we initialize the character
|
||||||
|
*/
|
||||||
|
constructor(playerUname, name, initialize) {
|
||||||
|
|
||||||
|
this.name = name;
|
||||||
|
|
||||||
|
// Initialize the unique name if this character.
|
||||||
|
//
|
||||||
|
// things to to hell if two characters with the same name are created at exactly the same time with the same random seed.
|
||||||
|
this._id = id.fromName(playerUname, name);
|
||||||
|
|
||||||
|
// should we skip initialization of this object
|
||||||
|
if (initialize !== true) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Initializing
|
||||||
|
//
|
||||||
|
|
||||||
|
// Rolling skills
|
||||||
|
|
||||||
|
/** @type {number} Awareness Skill */
|
||||||
|
this.awareness = roll.d6() + 2;
|
||||||
|
|
||||||
|
/** @type {number} Grit Skill */
|
||||||
|
this.grit = roll.d6() + 2;
|
||||||
|
|
||||||
|
/** @type {number} Knowledge Skill */
|
||||||
|
this.knowledge = roll.d6() + 2;
|
||||||
|
|
||||||
|
/** @type {number} Magic Skill */
|
||||||
|
this.magic = roll.d6() + 2;
|
||||||
|
|
||||||
|
/** @type {number} Melee Attack Skill */
|
||||||
|
this.meleeCombat = roll.d6() + 2;
|
||||||
|
|
||||||
|
/** @type {number} Ranged Attack Skill */
|
||||||
|
this.rangedCombat = roll.d6() + 2;
|
||||||
|
|
||||||
|
/** @type {number} Skulduggery Skill */
|
||||||
|
this.skulduggery = roll.d6() + 2;
|
||||||
|
|
||||||
|
switch (roll.d8()) {
|
||||||
|
case 1:
|
||||||
|
this.ancestry = "human";
|
||||||
|
// Humans get +1 to all skills
|
||||||
|
this.awareness++;
|
||||||
|
this.grit++;
|
||||||
|
this.knowledge++;
|
||||||
|
this.magic++;
|
||||||
|
this.meleeCombat++;
|
||||||
|
this.rangedCombat++;
|
||||||
|
this.skulduggery++;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.ancestry = "dwarven";
|
||||||
|
this.meleeCombat = Math.max(this.meleeCombat, 10);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
this.ancestry = "elven";
|
||||||
|
this.rangedCombat = Math.max(this.rangedCombat, 10);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
this.ancestry = "giant";
|
||||||
|
this.meleeCombat = Math.max(this.grit, 10);
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
this.ancestry = "Gnomish";
|
||||||
|
this.meleeCombat = Math.max(this.awareness, 10);
|
||||||
|
break;
|
||||||
|
case 6:
|
||||||
|
this.ancestry = "primordial";
|
||||||
|
this.meleeCombat = Math.max(this.magic, 10);
|
||||||
|
break;
|
||||||
|
case 7:
|
||||||
|
this.ancestry = "draconic";
|
||||||
|
this.meleeCombat = Math.max(this.knowledge, 10);
|
||||||
|
break;
|
||||||
|
case 8:
|
||||||
|
this.ancestry = "demonic";
|
||||||
|
this.meleeCombat = Math.max(this.skulduggery, 10);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error('Logic error, ancestry d8() roll was out of scope');
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
// Determine the character's Foundation
|
||||||
|
//
|
||||||
|
//
|
||||||
|
/** @type {string} Foundational background */
|
||||||
|
this.foundation = "";
|
||||||
|
const foundationRoll = roll.withSides(15);
|
||||||
|
switch (foundationRoll) {
|
||||||
|
case 1:
|
||||||
|
this.foundation = "brawler";
|
||||||
|
this.proficiencies.add("light_armor");
|
||||||
|
this.equipment.set("studded_leather", 1);
|
||||||
|
this.equipment.set("spiked_gauntlets", 1);
|
||||||
|
|
||||||
|
this.silver = 40;
|
||||||
|
this.maxHitPoints = this.currentHitPoints = 15;
|
||||||
|
this.itemSlots = 7;
|
||||||
|
this.meleeCombat = Math.max(this.meleeCombat, 10);
|
||||||
|
this.knowledge = Math.min(this.knowledge, 10);
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
this.foundation = "druid";
|
||||||
|
this.proficiencies.add("armor/natural");
|
||||||
|
this.equipment
|
||||||
|
.set("sickle", 1)
|
||||||
|
.set("poisoner's kit", 1)
|
||||||
|
.set("healer's kit", 1)
|
||||||
|
default:
|
||||||
|
this.foundation = "debug";
|
||||||
|
this.proficiencies.add("heavy_armor");
|
||||||
|
this.proficiencies.add("heavy_weapons");
|
||||||
|
this.equipment.set("debug_armor", 1);
|
||||||
|
this.equipment.set("longsword", 1);
|
||||||
|
this.silver = 666;
|
||||||
|
|
||||||
|
this.itemSlots = 10;
|
||||||
|
this.maxHitPoints = 20;
|
||||||
|
this.currentHitPoints = 20;
|
||||||
|
|
||||||
|
// default:
|
||||||
|
// throw new Error(`Logic error, foundation d15 roll of ${foundationRoll} roll was out of scope`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const c = new Character("username", "test", true);
|
||||||
|
|
||||||
|
console.log(c);
|
||||||
34
server/models/game.js
Normal file
34
server/models/game.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
/*
|
||||||
|
* The game object holds everything.
|
||||||
|
* All the locations, players, characters, items, npcs, quests, loot, etc.
|
||||||
|
*
|
||||||
|
* It is a pseudo-singleton in that you should only ever create one.
|
||||||
|
*
|
||||||
|
* Serializing this object effectively saves the game.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Character } from "./character";
|
||||||
|
import { ItemTemplate } from "./item";
|
||||||
|
|
||||||
|
class Game{
|
||||||
|
|
||||||
|
/** @type {Map<string,ItemTemplate>} List of all item templates in the game */
|
||||||
|
_itemTemplates = new Map();
|
||||||
|
|
||||||
|
/** @type {Map<string,Location>} The list of locations in the game */
|
||||||
|
_locations = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The characters in the game.
|
||||||
|
*
|
||||||
|
* @protected
|
||||||
|
* @type {Map<string,Character>}
|
||||||
|
*/
|
||||||
|
_characters = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @protected
|
||||||
|
* @type {Map<string,Player>} The list of users in the game
|
||||||
|
*/
|
||||||
|
_players = new Map();
|
||||||
|
}
|
||||||
125
server/models/item.js
Executable file
125
server/models/item.js
Executable file
@@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
45
server/models/location.js
Normal file
45
server/models/location.js
Normal file
@@ -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<string,Portal>} */
|
||||||
|
_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");
|
||||||
5
server/models/player.js
Normal file
5
server/models/player.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
export class Player{
|
||||||
|
_username;
|
||||||
|
_passwordHash;
|
||||||
|
alias;
|
||||||
|
}
|
||||||
27
server/models/portal.js
Normal file
27
server/models/portal.js
Normal file
@@ -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;
|
||||||
|
|
||||||
|
}
|
||||||
51
server/objects.adoc
Executable file
51
server/objects.adoc
Executable file
@@ -0,0 +1,51 @@
|
|||||||
|
= The world
|
||||||
|
|
||||||
|
== Character
|
||||||
|
|
||||||
|
* Resides in a <<Location>>.
|
||||||
|
* Can move from one <<Location>> to another by using <<Portal,Portals>>
|
||||||
|
* Has properties like
|
||||||
|
** Name, Race
|
||||||
|
** Money, XP, Level
|
||||||
|
** Strength Dex, Etc.
|
||||||
|
* Can <<Encounter>> stuff.
|
||||||
|
* Can talk (chat) with other characters in the same <<Location>>.
|
||||||
|
|
||||||
|
== 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 <<Portal,Portals>>
|
||||||
|
** `key`: string, typically `"north"`, `"east"`, etc., but
|
||||||
|
it can also be more interesting such as `"trap door"`.
|
||||||
|
** `value`: A <<Portal>>.
|
||||||
|
* Does not need know about inbound portals.
|
||||||
|
* Can contain (or generate) Encounters.
|
||||||
|
|
||||||
|
|
||||||
|
== Portal
|
||||||
|
* A *One Way* transport to a given <<Location>>.
|
||||||
|
* 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 <<Location>>.
|
||||||
|
* ArrivalDescription (optional) A text that overrides the default text that you
|
||||||
|
would normally see upon entering a <<Location>>. If the player executes a
|
||||||
|
`look` command, the normal description will be shown.
|
||||||
|
* TargetSight (bool,false) Allows you to `look` into the target <<Location>>,
|
||||||
|
getting an idea of what you are about to get into.
|
||||||
|
* A door between two rooms is typically represented via two <<Portal,Portals>>;
|
||||||
|
one one each direction.
|
||||||
|
|
||||||
|
== Encounter
|
||||||
|
|
||||||
|
* Can be one (or more) of:
|
||||||
|
** Roleplay Encounter (NPC)
|
||||||
|
** Combat Encounter (monsters)
|
||||||
|
** Environmental Encounter (Trap, Puzzle)
|
||||||
|
** Loot
|
||||||
0
server/package-lock.json
generated
Normal file → Executable file
0
server/package-lock.json
generated
Normal file → Executable file
41
server/package.json
Normal file → Executable file
41
server/package.json
Normal file → Executable file
@@ -1,22 +1,23 @@
|
|||||||
{
|
{
|
||||||
"name": "websocket-mud",
|
"name": "websocket-mud",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "A Multi-User Dungeon game using WebSockets and Node.js",
|
"description": "A Multi-User Dungeon game using WebSockets and Node.js",
|
||||||
"main": "server.js",
|
"main": "server.js",
|
||||||
"scripts": {
|
"type": "module",
|
||||||
"start": "node server.js",
|
"scripts": {
|
||||||
"dev": "nodemon server.js"
|
"start": "node server.js",
|
||||||
},
|
"dev": "nodemon server.js"
|
||||||
"keywords": ["mud", "websocket", "game", "multiplayer"],
|
},
|
||||||
"author": "Your Name",
|
"keywords": ["mud", "websocket", "game", "multiplayer"],
|
||||||
"license": "MIT",
|
"author": "Your Name",
|
||||||
"dependencies": {
|
"license": "MIT",
|
||||||
"ws": "^8.14.2"
|
"dependencies": {
|
||||||
},
|
"ws": "^8.14.2"
|
||||||
"devDependencies": {
|
},
|
||||||
"nodemon": "^3.0.1"
|
"devDependencies": {
|
||||||
},
|
"nodemon": "^3.0.1"
|
||||||
"engines": {
|
},
|
||||||
"node": ">=14.0.0"
|
"engines": {
|
||||||
}
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ const fs = require('fs');
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Player
|
* Player
|
||||||
*
|
|
||||||
* @property WebSocket websocket
|
* @property WebSocket websocket
|
||||||
*/
|
*/
|
||||||
class Player {
|
class Player {
|
||||||
|
|||||||
13
server/utils/dice.js
Normal file
13
server/utils/dice.js
Normal file
@@ -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);
|
||||||
|
}
|
||||||
|
|
||||||
12
server/utils/helpers.js
Executable file
12
server/utils/helpers.js
Executable file
@@ -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);
|
||||||
|
}
|
||||||
26
server/utils/id.js
Normal file
26
server/utils/id.js
Normal file
@@ -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();
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user