things+stuff
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import * as roll from "../utils/dice.js";
|
||||
import * as id from "../utils/id.js";
|
||||
import { Item } from "./item.js";
|
||||
|
||||
/**
|
||||
* A playable character.
|
||||
@@ -14,28 +15,31 @@ export class Character {
|
||||
* @protected
|
||||
* @type {number} The number of XP the character has.
|
||||
*/
|
||||
_xp = 0;
|
||||
get xp() {
|
||||
return this._xp;
|
||||
}
|
||||
xp = 0;
|
||||
|
||||
/** @protected @type {number} The character's level. */
|
||||
_level = 1;
|
||||
get level() {
|
||||
return this._level;
|
||||
}
|
||||
level = 1;
|
||||
|
||||
/** @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;
|
||||
}
|
||||
/** @type {number} Awareness Skill */
|
||||
awareness;
|
||||
|
||||
/** @protected @type {string} username of the player that owns this character. */
|
||||
_username;
|
||||
get username() {
|
||||
return this._username;
|
||||
}
|
||||
/** @type {number} Grit Skill */
|
||||
grit;
|
||||
|
||||
/** @type {number} Knowledge Skill */
|
||||
knowledge;
|
||||
|
||||
/** @type {number} Magic Skill */
|
||||
magic;
|
||||
|
||||
/** @type {number} Melee Attack Skill */
|
||||
meleeCombat;
|
||||
|
||||
/** @type {number} Ranged Attack Skill */
|
||||
rangedCombat;
|
||||
|
||||
/** @type {number} Skulduggery Skill */
|
||||
skulduggery;
|
||||
|
||||
/** @type {string} Bloodline background */
|
||||
ancestry;
|
||||
@@ -56,144 +60,40 @@ export class Character {
|
||||
itemSlots;
|
||||
|
||||
/** @type {Set<string>} Things the character is particularly proficient at. */
|
||||
proficiencies = new Set();
|
||||
skills = new Set();
|
||||
|
||||
/** @type {Map<string,number} Things the character is particularly proficient at. */
|
||||
equipment = new Map();
|
||||
/** @type {Map<Item,number} Things the character is particularly proficient at. */
|
||||
items = new Map();
|
||||
|
||||
/**
|
||||
* @param {string} username 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=false} initialize Should we initialize the character
|
||||
*/
|
||||
constructor(username, name, initialize) {
|
||||
constructor(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(username, 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) // can apply to weapon, food, drink. Can recharge in the forest
|
||||
.set("healer's kit", 1); // provide fast out-of-combat healing. Can recharge in forest.
|
||||
this.silver = 10;
|
||||
this.maxHitPoints = this.currentHitPoints = 10;
|
||||
this.itemSlots = 5;
|
||||
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`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Add an item to the equipment list
|
||||
* @param {Item} item
|
||||
* @param {number} count
|
||||
*
|
||||
* Maybe return the accumulated ItemSlots used?
|
||||
*/
|
||||
addItem(item, count = 1) {
|
||||
if (!Number.isInteger(count)) {
|
||||
throw new Error("Number must be an integer");
|
||||
}
|
||||
if (!(item instanceof Item)) {
|
||||
console.debug("bad item", item);
|
||||
throw new Error("item must be an instance of Item!");
|
||||
}
|
||||
if (count <= 0) {
|
||||
throw new Error("Number must be > 0");
|
||||
}
|
||||
|
||||
const existingItemCount = this.items.get(item) || 0;
|
||||
|
||||
this.items.set(item, count + existingItemCount);
|
||||
}
|
||||
|
||||
// todo removeItem(item, count)
|
||||
}
|
||||
|
||||
@@ -7,15 +7,14 @@
|
||||
* Serializing this object effectively saves the game.
|
||||
*/
|
||||
|
||||
import { miniUid } from "../utils/id.js";
|
||||
import { isIdSane, miniUid } from "../utils/id.js";
|
||||
import { Character } from "./character.js";
|
||||
import { ItemTemplate } from "./item.js";
|
||||
import { ItemAttributes, ItemBlueprint } from "./item.js";
|
||||
import { Player } from "./player.js";
|
||||
|
||||
export class Game {
|
||||
|
||||
/** @type {Map<string,ItemTemplate>} List of all item templates in the game */
|
||||
_itemTemplates = new Map();
|
||||
/** @type {Map<string,ItemBlueprint>} List of all item blueprints in the game */
|
||||
_itemBlueprints = new Map();
|
||||
|
||||
/** @type {Map<string,Location>} The list of locations in the game */
|
||||
_locations = new Map();
|
||||
@@ -40,10 +39,10 @@ export class Game {
|
||||
|
||||
/**
|
||||
* Atomic player creation.
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string?} passwordHash
|
||||
* @param {string?} salt
|
||||
*
|
||||
* @param {string} username
|
||||
* @param {string?} passwordHash
|
||||
* @param {string?} salt
|
||||
*
|
||||
* @returns {Player|null} Returns the player if username wasn't already taken, or null otherwise.
|
||||
*/
|
||||
@@ -55,7 +54,7 @@ export class Game {
|
||||
const player = new Player(
|
||||
username,
|
||||
typeof passwordHash === "string" ? passwordHash : "",
|
||||
typeof salt === "string" && salt.length > 0 ? salt : miniUid()
|
||||
typeof salt === "string" && salt.length > 0 ? salt : miniUid(),
|
||||
);
|
||||
|
||||
this._players.set(username, player);
|
||||
@@ -64,38 +63,45 @@ export class Game {
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an ItemTemplate with a given ID
|
||||
*
|
||||
* @param {string} id
|
||||
* @param {object} attributes
|
||||
*
|
||||
* @returns {ItemTemplate|false}
|
||||
*/
|
||||
createItemTemplate(id, attributes) {
|
||||
|
||||
if (typeof id !== "string" || !id) {
|
||||
throw new Error("Invalid id!");
|
||||
* Create an ItemBlueprint with a given blueprintId
|
||||
*
|
||||
* @param {string} blueprintId
|
||||
* @param {ItemAttributes} attributes
|
||||
*
|
||||
* @returns {ItemBlueprint|false}
|
||||
*/
|
||||
addItemBlueprint(blueprintId, attributes) {
|
||||
console.log(attributes);
|
||||
if (typeof blueprintId !== "string" || !blueprintId) {
|
||||
throw new Error("Invalid blueprintId!");
|
||||
}
|
||||
|
||||
if (this._itemTemplates.has(id)) {
|
||||
return false;
|
||||
const existing = this._itemBlueprints.get(blueprintId);
|
||||
|
||||
if (existing) {
|
||||
console.debug("we tried to create the same item blueprint more than once", blueprintId, attributes);
|
||||
return existing;
|
||||
}
|
||||
|
||||
/** @type {ItemTemplate} */
|
||||
const result = new ItemTemplate(id, attributes.name, attributes.itemSlots);
|
||||
attributes.blueprintId = blueprintId;
|
||||
|
||||
for (const key of Object.keys(result)) {
|
||||
if (key === "id") {
|
||||
continue;
|
||||
}
|
||||
if (key in attributes) {
|
||||
result[key] = attributes[key];
|
||||
}
|
||||
}
|
||||
const result = new ItemBlueprint(attributes);
|
||||
|
||||
|
||||
this._itemTemplates.set(id, result);
|
||||
this._itemBlueprints.set(blueprintId, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} blueprintId
|
||||
* @returns {ItemBlueprint?}
|
||||
*/
|
||||
getItemBlueprint(blueprintId) {
|
||||
if (!isIdSane(blueprintId)) {
|
||||
throw new Error(`blueprintId >>${blueprintId}<< is insane!`);
|
||||
}
|
||||
const tpl = this._itemBlueprints.get(blueprintId);
|
||||
|
||||
return tpl || undefined;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,10 @@
|
||||
/**
|
||||
* 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.
|
||||
* Abstract class for documentation purposes.
|
||||
* @abstract
|
||||
*/
|
||||
export class ItemTemplate {
|
||||
/** @constant @readonly @type {string} Item's machine-friendly name */
|
||||
id;
|
||||
export class ItemAttributes {
|
||||
/** @constant @readonly @type {string} Machine-friendly name for the blueprint */
|
||||
blueprintId;
|
||||
|
||||
/** @constant @readonly @type {string} Item's human-friendly name */
|
||||
name;
|
||||
@@ -18,9 +16,9 @@ export class ItemTemplate {
|
||||
itemSlots;
|
||||
|
||||
/** @constant @readonly @type {number?} How much damage (if any) does this item deal */
|
||||
damage;
|
||||
baseDamage;
|
||||
|
||||
/** @constant @readonly @type {string?} Which special effect is triggered when successfull attacking with this item? */
|
||||
/** @constant @readonly @type {string?} Which special effect is triggered when successful attacking with this item? */
|
||||
specialEffect;
|
||||
|
||||
/** @constant @readonly @type {boolean?} Can this item be used as a melee weapon? */
|
||||
@@ -29,84 +27,83 @@ export class ItemTemplate {
|
||||
/** @constant @readonly @type {boolean?} Can this item be used as a ranged weapon? */
|
||||
ranged;
|
||||
|
||||
/** @readonly @type {number} How many extra HP do you have when oyu wear this armor. */
|
||||
armorHitPoints;
|
||||
|
||||
/** @constant @readonly @type {string?} Type of ammo that this item is, or that this item uses */
|
||||
ammoType;
|
||||
|
||||
/** @readonly @type {number} how much is left in this item. (Potions can have many doses and quivers many arrows) */
|
||||
count;
|
||||
|
||||
/** @readonly @type {number} Some items (quivers) can be replenished, so how much can this quiver/potion/ration pack hold */
|
||||
maxCount;
|
||||
|
||||
/** @constant @readonly @type {string[]} Type of ammo that this item is, or that this item uses */
|
||||
skills = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Item blueprints are the built-in basic items of the game.
|
||||
* A character cannot directly own one of these items,
|
||||
* they can only own Items, and ItemBlueprints can be used to
|
||||
* generate these Items.
|
||||
*/
|
||||
export class ItemBlueprint extends ItemAttributes {
|
||||
/**
|
||||
* Constructor
|
||||
*
|
||||
* @param {string=null} id Item's machine-friendly name.
|
||||
* @param {string} name. The Item's Name.
|
||||
* @param {number} itemSlots number of item slots the item takes up in a character's inventory.
|
||||
* @param {object} o Object whose attributes we copy
|
||||
*/
|
||||
constructor(id, name, itemSlots) {
|
||||
constructor(o) {
|
||||
super();
|
||||
|
||||
if (typeof id !== "string" || id.length < 1) {
|
||||
throw new Error("id must be a string!");
|
||||
if (typeof o.blueprintId !== "string" || o.name.length < 1) {
|
||||
throw new Error("blueprintId must be a string, but " + typeof o.blueprintId + " given.");
|
||||
}
|
||||
|
||||
if (typeof name !== "string" || name.length < 1) {
|
||||
throw new Error("Name must be a string, but " + typeof name + " given.");
|
||||
if (typeof o.name !== "string" || o.name.length < 1) {
|
||||
throw new Error("Name must be a string, but " + typeof o.name + " given.");
|
||||
}
|
||||
|
||||
if (!Number.isFinite(itemSlots)) {
|
||||
if (!Number.isFinite(o.itemSlots)) {
|
||||
throw new Error("itemSlots must be a finite number!");
|
||||
}
|
||||
|
||||
this.name = name;
|
||||
this.id = id;
|
||||
this.itemSlots = Number(itemSlots);
|
||||
o.itemSlots = Number(o.itemSlots);
|
||||
|
||||
for (const [key, _] of Object.entries(this)) {
|
||||
if (o[key] !== "undefied") {
|
||||
this[key] = o[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//
|
||||
// Spawn a new item!
|
||||
// Spawn a new non-unique item!
|
||||
/** @returns {Item} */
|
||||
createItem() {
|
||||
return new ChracterItem(
|
||||
this.id,
|
||||
this.name,
|
||||
this.description,
|
||||
this.itemSlots,
|
||||
);
|
||||
const item = new Item();
|
||||
|
||||
for (const [key, value] of Object.entries(this)) {
|
||||
item[key] = value;
|
||||
}
|
||||
|
||||
item.blueprintId = this.blueprintId;
|
||||
|
||||
return item;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Characters can only own CharacterItems.
|
||||
* An object of this class represents a single instance
|
||||
* of a given item in the game. It can be a shortsword, or a potion,
|
||||
* or another, different shortsword that belongs to another character, etc.
|
||||
*
|
||||
* 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.
|
||||
* If a character has two identical potions of healing, they are each represented
|
||||
* by an object of this class.
|
||||
* The only notable tweak to this rule is collective items like quivers that have
|
||||
* arrows that are consumed. In this case, each individual arrow is not tracked
|
||||
* as its own entity, only the quiver is tracked.
|
||||
*/
|
||||
export class CharacterItem {
|
||||
/** @type {ItemTemplate|null} The template that created this item. Null if no such template exists [anymore]. */
|
||||
itemTemplate; // 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;
|
||||
}
|
||||
}
|
||||
export class Item extends ItemAttributes {}
|
||||
|
||||
@@ -9,37 +9,37 @@ import { Portal } from "./portal";
|
||||
* or magical portals to distant locations.
|
||||
*/
|
||||
export class Location {
|
||||
/** @protected @type string */
|
||||
_id;
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
/** @protected @type string */
|
||||
_id;
|
||||
get id() {
|
||||
return this._id;
|
||||
}
|
||||
|
||||
/** @protected @type string */
|
||||
_name;
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
/** @protected @type string */
|
||||
_name;
|
||||
get name() {
|
||||
return this._name;
|
||||
}
|
||||
|
||||
/** @protected @type string */
|
||||
_description;
|
||||
get description() {
|
||||
return this._description;
|
||||
}
|
||||
/** @protected @type string */
|
||||
_description;
|
||||
get description() {
|
||||
return this._description;
|
||||
}
|
||||
|
||||
/** @protected @type {Map<string,Portal>} */
|
||||
_portals = new Map();
|
||||
get portals() {
|
||||
return this._portals;
|
||||
}
|
||||
/** @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;
|
||||
}
|
||||
/**
|
||||
*/
|
||||
constructor(id, name, description) {
|
||||
this._id = id;
|
||||
this._name = name;
|
||||
this._description = description;
|
||||
}
|
||||
}
|
||||
|
||||
const l = new Location("foo", "bar", "baz");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import WebSocket from "ws";
|
||||
import { Character } from "./character.js";
|
||||
import { Config } from "./../config.js";
|
||||
|
||||
/**
|
||||
* Player Account.
|
||||
@@ -7,66 +8,84 @@ import { Character } from "./character.js";
|
||||
* Contain persistent player account info.
|
||||
*/
|
||||
export class Player {
|
||||
/** @protected @type {string} unique username */
|
||||
_username;
|
||||
get username() {
|
||||
return this._username;
|
||||
}
|
||||
|
||||
/** @protected @type {string} unique username */
|
||||
_username;
|
||||
get username() {
|
||||
return this._username;
|
||||
/** @protected @type {string} */
|
||||
_passwordHash;
|
||||
get passwordHash() {
|
||||
return this._passwordHash;
|
||||
}
|
||||
|
||||
/** @protected @type {string} random salt used for hashing */
|
||||
_salt;
|
||||
get salt() {
|
||||
return this._salt;
|
||||
}
|
||||
|
||||
/** @protected @type {Date} */
|
||||
_createdAt = new Date();
|
||||
get createdAt() {
|
||||
return this._createdAt;
|
||||
}
|
||||
|
||||
/** @type {Date} */
|
||||
blockedUntil;
|
||||
|
||||
/** @type {Date|null} Date of the player's last websocket message. */
|
||||
lastActivityAt = null;
|
||||
|
||||
/** @type {Date|null} Date of the player's last login. */
|
||||
lastSucessfulLoginAt = null;
|
||||
|
||||
/** @type {number} Number of successful logins on this character */
|
||||
successfulLogins = 0;
|
||||
|
||||
/** @type {number} Number of failed login attempts since the last good login attempt */
|
||||
failedPasswordsSinceLastLogin = 0;
|
||||
|
||||
/** @protected @type {Set<Character>} */
|
||||
_characters = new Set(); // should this be a WeakSet? After all if the player is removed, their items might remain in the system, right?
|
||||
get characters() {
|
||||
return this._characters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} username
|
||||
* @param {string} passwordHash
|
||||
* @param {string} salt
|
||||
*/
|
||||
constructor(username, passwordHash, salt) {
|
||||
this._username = username;
|
||||
this._passwordHash = passwordHash;
|
||||
this._salt = salt;
|
||||
this._createdAt = new Date();
|
||||
}
|
||||
|
||||
setPasswordHash(hashedPassword) {
|
||||
this._passwordHash = hashedPassword;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a character to the player's party
|
||||
*
|
||||
* @param {Character} character
|
||||
* @returns {number|false} the new size of the players party if successful, or false if the character could not be added.
|
||||
*/
|
||||
addCharacter(character) {
|
||||
if (this._characters.has(character)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @protected @type {string} */
|
||||
_passwordHash;
|
||||
get passwordHash() {
|
||||
return this._passwordHash;
|
||||
if (this._characters.size >= Config.maxPartySize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/** @protected @type {string} random salt used for hashing */
|
||||
_salt;
|
||||
get salt() {
|
||||
return this._salt;
|
||||
}
|
||||
this._characters.add(character);
|
||||
|
||||
/** @protected @type {Date} */
|
||||
_createdAt = new Date();
|
||||
get createdAt() {
|
||||
return this._createdAt;
|
||||
}
|
||||
|
||||
/** @type {Date} */
|
||||
blockedUntil;
|
||||
|
||||
|
||||
/** @type {Date|null} Date of the player's last websocket message. */
|
||||
lastActivityAt = null;
|
||||
|
||||
/** @type {Date|null} Date of the player's last login. */
|
||||
lastSucessfulLoginAt = null;
|
||||
|
||||
/** @type {number} Number of successful logins on this character */
|
||||
successfulLogins = 0;
|
||||
|
||||
/** @type {number} Number of failed login attempts since the last good login attempt */
|
||||
failedPasswordsSinceLastLogin = 0;
|
||||
|
||||
/** @protected @type {Set<Character>} */
|
||||
_characters = new Set(); // should this be a WeakSet? After all if the player is removed, their items might remain in the system, right?
|
||||
get characters() {
|
||||
return this._characters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} username
|
||||
* @param {string} passwordHash
|
||||
* @param {string} salt
|
||||
*/
|
||||
constructor(username, passwordHash, salt) {
|
||||
this._username = username;
|
||||
this._passwordHash = passwordHash;
|
||||
this._salt = salt;
|
||||
this._createdAt = new Date();
|
||||
}
|
||||
|
||||
setPasswordHash(hashedPassword) {
|
||||
this._passwordHash = hashedPassword;
|
||||
}
|
||||
return this._characters.size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,18 +8,18 @@
|
||||
* @todo Add encounters to portals
|
||||
*/
|
||||
export class Portal {
|
||||
/**
|
||||
* Target Location.
|
||||
*/
|
||||
_targetLocationId;
|
||||
/**
|
||||
* 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 inspect the portal from the source location.
|
||||
*/
|
||||
_description;
|
||||
|
||||
/**
|
||||
* Description shown to the player when they traverse the portal.
|
||||
*/
|
||||
_traversalDescription;
|
||||
/**
|
||||
* Description shown to the player when they traverse the portal.
|
||||
*/
|
||||
_traversalDescription;
|
||||
}
|
||||
|
||||
@@ -1,131 +1,129 @@
|
||||
import WebSocket from 'ws';
|
||||
import { Game } from './game.js';
|
||||
import { Player } from './player.js';
|
||||
import { StateInterface } from '../states/interface.js';
|
||||
import * as msg from '../utils/messages.js';
|
||||
import figlet from 'figlet';
|
||||
import WebSocket from "ws";
|
||||
import { Game } from "./game.js";
|
||||
import { Player } from "./player.js";
|
||||
import { StateInterface } from "../states/interface.js";
|
||||
import * as msg from "../utils/messages.js";
|
||||
import figlet from "figlet";
|
||||
|
||||
export class Session {
|
||||
/** @protected @type {StateInterface} */
|
||||
_state;
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
|
||||
/** @protected @type {StateInterface} */
|
||||
_state;
|
||||
get state() {
|
||||
return this._state;
|
||||
/** @protected @type {Game} */
|
||||
_game;
|
||||
get game() {
|
||||
return this._game;
|
||||
}
|
||||
|
||||
/** @type {Player} */
|
||||
_player;
|
||||
get player() {
|
||||
return this._player;
|
||||
}
|
||||
|
||||
/** @param {Player} player */
|
||||
set player(player) {
|
||||
if (player instanceof Player) {
|
||||
this._player = player;
|
||||
return;
|
||||
}
|
||||
|
||||
/** @protected @type {Game} */
|
||||
_game;
|
||||
get game() {
|
||||
return this._game;
|
||||
if (player === null) {
|
||||
this._player = null;
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {Player} */
|
||||
_player;
|
||||
get player() {
|
||||
return this._player;
|
||||
throw Error(
|
||||
`Can only set player to null or instance of Player, but received ${typeof player}`,
|
||||
);
|
||||
}
|
||||
|
||||
/** @type {WebSocket} */
|
||||
_websocket;
|
||||
|
||||
/**
|
||||
* @param {WebSocket} websocket
|
||||
* @param {Game} game
|
||||
*/
|
||||
constructor(websocket, game) {
|
||||
this._websocket = websocket;
|
||||
this._game = game;
|
||||
}
|
||||
|
||||
/** Close the session and websocket */
|
||||
close() {
|
||||
this._websocket.close();
|
||||
this._player = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message via our websocket.
|
||||
*
|
||||
* @param {string|number} messageType
|
||||
* @param {...any} args
|
||||
*/
|
||||
send(messageType, ...args) {
|
||||
this._websocket.send(JSON.stringify([messageType, ...args]));
|
||||
}
|
||||
|
||||
sendFigletMessage(message) {
|
||||
console.debug("sendFigletMessage('%s')", message);
|
||||
this.sendMessage(figlet.textSync(message), { preformatted: true });
|
||||
}
|
||||
|
||||
/** @param {string} message Message to display to player */
|
||||
sendMessage(message, ...args) {
|
||||
if (message.length === 0) {
|
||||
console.debug("sending a zero-length message, weird");
|
||||
}
|
||||
|
||||
/** @param {Player} player */
|
||||
set player(player) {
|
||||
|
||||
if (player instanceof Player) {
|
||||
this._player = player;
|
||||
return;
|
||||
}
|
||||
|
||||
if (player === null) {
|
||||
this._player = null;
|
||||
return;
|
||||
}
|
||||
|
||||
throw Error(`Can only set player to null or instance of Player, but received ${typeof player}`);
|
||||
if (Array.isArray(message)) {
|
||||
message = message.join("\n");
|
||||
}
|
||||
this.send(msg.MESSAGE, message, ...args);
|
||||
}
|
||||
|
||||
|
||||
/** @type {WebSocket} */
|
||||
_websocket;
|
||||
|
||||
/**
|
||||
* @param {WebSocket} websocket
|
||||
* @param {Game} game
|
||||
*/
|
||||
constructor(websocket, game) {
|
||||
this._websocket = websocket;
|
||||
this._game = game;
|
||||
/**
|
||||
* @param {string} type prompt type (username, password, character name, etc.)
|
||||
* @param {string|string[]} message The prompting message (please enter your character's name)
|
||||
* @param {string} tag helps with message routing and handling.
|
||||
*/
|
||||
sendPrompt(type, message, tag = "", ...args) {
|
||||
if (Array.isArray(message)) {
|
||||
message = message.join("\n");
|
||||
}
|
||||
this.send(msg.PROMPT, type, message, tag, ...args);
|
||||
}
|
||||
|
||||
/** Close the session and websocket */
|
||||
close() {
|
||||
this._websocket.close();
|
||||
this._player = null;
|
||||
/** @param {string} message The error message to display to player */
|
||||
sendError(message, ...args) {
|
||||
this.send(msg.ERROR, message, ...args);
|
||||
}
|
||||
|
||||
/** @param {string} message The error message to display to player */
|
||||
sendDebug(message, ...args) {
|
||||
this.send(msg.DEBUG, message, ...args);
|
||||
}
|
||||
|
||||
/** @param {string} message The calamitous error to display to player */
|
||||
sendCalamity(message, ...args) {
|
||||
this.send(msg.CALAMITY, message, ...args);
|
||||
}
|
||||
|
||||
sendSystemMessage(arg0, ...rest) {
|
||||
this.send(msg.SYSTEM, arg0, ...rest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StateInterface} state
|
||||
*/
|
||||
setState(state) {
|
||||
this._state = state;
|
||||
console.debug("changing state", state.constructor.name);
|
||||
if (typeof state.onAttach === "function") {
|
||||
state.onAttach();
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message via our websocket.
|
||||
*
|
||||
* @param {string|number} messageType
|
||||
* @param {...any} args
|
||||
*/
|
||||
send(messageType, ...args) {
|
||||
this._websocket.send(JSON.stringify([messageType, ...args]));
|
||||
}
|
||||
|
||||
sendFigletMessage(message) {
|
||||
console.debug("sendFigletMessage('%s')", message);
|
||||
this.sendMessage(figlet.textSync(message), { preformatted: true });
|
||||
}
|
||||
|
||||
/** @param {string} message Message to display to player */
|
||||
sendMessage(message, ...args) {
|
||||
if (message.length === 0) {
|
||||
console.debug("sending a zero-length message, weird");
|
||||
}
|
||||
if (Array.isArray(message)) {
|
||||
message = message.join("\n");
|
||||
}
|
||||
this.send(msg.MESSAGE, message, ...args);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} type prompt type (username, password, character name, etc.)
|
||||
* @param {string|string[]} message The prompting message (please enter your character's name)
|
||||
* @param {string} tag helps with message routing and handling.
|
||||
*/
|
||||
sendPrompt(type, message, tag="default", ...args) {
|
||||
if (Array.isArray(message)) {
|
||||
message = message.join("\n");
|
||||
}
|
||||
this.send(msg.PROMPT, type, message, tag, ...args);
|
||||
}
|
||||
|
||||
/** @param {string} message The error message to display to player */
|
||||
sendError(message, ...args) {
|
||||
this.send(msg.ERROR, message, ...args);
|
||||
}
|
||||
|
||||
/** @param {string} message The error message to display to player */
|
||||
sendDebug(message, ...args) {
|
||||
this.send(msg.DEBUG, message, ...args);
|
||||
}
|
||||
|
||||
/** @param {string} message The calamitous error to display to player */
|
||||
sendCalamity(message, ...args) {
|
||||
this.send(msg.CALAMITY, message, ...args);
|
||||
}
|
||||
|
||||
sendSystemMessage(arg0, ...rest) {
|
||||
this.send(msg.SYSTEM, arg0, ...rest);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {StateInterface} state
|
||||
*/
|
||||
setState(state) {
|
||||
this._state = state;
|
||||
console.debug("changing state", state.constructor.name);
|
||||
if (typeof state.onAttach === "function") {
|
||||
state.onAttach();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user