stuffy
This commit is contained in:
@@ -7,6 +7,7 @@
|
||||
* Serializing this object effectively saves the game.
|
||||
*/
|
||||
|
||||
import { Config } from "../config.js";
|
||||
import { isIdSane, miniUid } from "../utils/id.js";
|
||||
import { Xorshift32 } from "../utils/random.js";
|
||||
import { Character } from "./character.js";
|
||||
@@ -14,6 +15,8 @@ import { ItemAttributes, ItemBlueprint } from "./item.js";
|
||||
import { Player } from "./player.js";
|
||||
|
||||
export class Game {
|
||||
_counter = 1_000_000;
|
||||
|
||||
/** @type {Map<string,ItemBlueprint>} List of all item blueprints in the game */
|
||||
_itemBlueprints = new Map();
|
||||
|
||||
@@ -34,22 +37,21 @@ export class Game {
|
||||
*/
|
||||
_players = new Map();
|
||||
|
||||
|
||||
/** @protected @type {Xorshift32} */
|
||||
_rng;
|
||||
_random;
|
||||
|
||||
/** @type {Xorshift32} */
|
||||
get rng() {
|
||||
return this._rng;
|
||||
get random() {
|
||||
return this._random;
|
||||
}
|
||||
|
||||
/** @param {number} rngSeed Seed number used for randomization */
|
||||
constructor(rngSeed) {
|
||||
if (!Number.isInteger(rngSeed)) {
|
||||
throw new Error("rngSeed must be an integer");
|
||||
}
|
||||
constructor() {
|
||||
this.rngSeed = Date.now();
|
||||
}
|
||||
|
||||
this._rng = new Xorshift32(rngSeed);
|
||||
set rngSeed(rngSeed) {
|
||||
this._random = new Xorshift32(rngSeed);
|
||||
}
|
||||
|
||||
getPlayer(username) {
|
||||
@@ -90,7 +92,6 @@ export class Game {
|
||||
* @returns {ItemBlueprint|false}
|
||||
*/
|
||||
addItemBlueprint(blueprintId, attributes) {
|
||||
console.log(attributes);
|
||||
if (typeof blueprintId !== "string" || !blueprintId) {
|
||||
throw new Error("Invalid blueprintId!");
|
||||
}
|
||||
|
||||
4
server/models/globals.js
Executable file
4
server/models/globals.js
Executable file
@@ -0,0 +1,4 @@
|
||||
import { Game } from "./game.js";
|
||||
|
||||
/** @constant @readonly @type {Game} Global instance of Game */
|
||||
export const gGame = new Game();
|
||||
@@ -1,6 +1,7 @@
|
||||
import WebSocket from "ws";
|
||||
import { Character } from "./character.js";
|
||||
import { Config } from "./../config.js";
|
||||
import { Scene } from "../scenes/scene.js";
|
||||
|
||||
/**
|
||||
* Player Account.
|
||||
@@ -8,84 +9,90 @@ import { Config } from "./../config.js";
|
||||
* Contain persistent player account info.
|
||||
*/
|
||||
export class Player {
|
||||
/** @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} unique username */
|
||||
_username;
|
||||
get username() {
|
||||
return this._username;
|
||||
}
|
||||
|
||||
if (this._characters.size >= Config.maxPartySize) {
|
||||
return false;
|
||||
/** @protected @type {string} */
|
||||
_passwordHash;
|
||||
get passwordHash() {
|
||||
return this._passwordHash;
|
||||
}
|
||||
|
||||
this._characters.add(character);
|
||||
/** @protected @type {string} random salt used for hashing */
|
||||
_salt;
|
||||
get salt() {
|
||||
return this._salt;
|
||||
}
|
||||
|
||||
return this._characters.size;
|
||||
}
|
||||
/** @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;
|
||||
|
||||
/** @type {boolean} Is the player logged in right now? */
|
||||
loggedIn = false;
|
||||
|
||||
/** @type {Scene} The scene the player was before they logged out */
|
||||
latestScene;
|
||||
|
||||
/** @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;
|
||||
}
|
||||
|
||||
if (this._characters.size >= Config.maxPartySize) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._characters.add(character);
|
||||
|
||||
return this._characters.size;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,129 +1,142 @@
|
||||
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 { mustBeString, mustBe } from "../utils/mustbe.js";
|
||||
import { Scene } from "../scenes/scene.js";
|
||||
import { gGame } from "./globals.js";
|
||||
|
||||
export class Session {
|
||||
/** @protected @type {StateInterface} */
|
||||
_state;
|
||||
get state() {
|
||||
return this._state;
|
||||
}
|
||||
/** @type {WebSocket} */
|
||||
_websocket;
|
||||
|
||||
/** @protected @type {Game} */
|
||||
_game;
|
||||
get game() {
|
||||
return this._game;
|
||||
}
|
||||
/** @protected @type {Scene} */
|
||||
_scene;
|
||||
|
||||
/** @type {Player} */
|
||||
_player;
|
||||
get player() {
|
||||
return this._player;
|
||||
}
|
||||
|
||||
/** @param {Player} player */
|
||||
set player(player) {
|
||||
if (player instanceof Player) {
|
||||
this._player = player;
|
||||
return;
|
||||
/** @readonly @constant @type {Scene} */
|
||||
get scene() {
|
||||
return this._scene;
|
||||
}
|
||||
|
||||
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}`,
|
||||
);
|
||||
}
|
||||
/** @param {Player} player */
|
||||
set player(player) {
|
||||
if (player instanceof Player) {
|
||||
this._player = player;
|
||||
return;
|
||||
}
|
||||
|
||||
/** @type {WebSocket} */
|
||||
_websocket;
|
||||
if (player === null) {
|
||||
this._player = null;
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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");
|
||||
throw Error(`Can only set player to null or instance of Player, but received ${typeof player}`);
|
||||
}
|
||||
if (Array.isArray(message)) {
|
||||
message = message.join("\n");
|
||||
|
||||
/**
|
||||
* @param {WebSocket} websocket
|
||||
*/
|
||||
constructor(websocket) {
|
||||
this._websocket = websocket;
|
||||
}
|
||||
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 = "", ...args) {
|
||||
if (Array.isArray(message)) {
|
||||
message = message.join("\n");
|
||||
/**
|
||||
* @param {Scene} scene
|
||||
*/
|
||||
setScene(scene) {
|
||||
console.debug("changing scene", scene.constructor.name);
|
||||
if (!(scene instanceof Scene)) {
|
||||
throw new Error(`Expected instance of Scene, got a ${typeof scene}: >>${scene}<<`);
|
||||
}
|
||||
this._scene = scene;
|
||||
scene.execute(this);
|
||||
}
|
||||
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();
|
||||
/** Close the session and websocket */
|
||||
close() {
|
||||
if (this._websocket) {
|
||||
this._websocket.close();
|
||||
this._websocket = null;
|
||||
}
|
||||
this._player = null;
|
||||
this._scene = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a message via our websocket.
|
||||
*
|
||||
* @param {string|number} messageType
|
||||
* @param {...any} args
|
||||
*/
|
||||
send(messageType, ...args) {
|
||||
if (!this._websocket) {
|
||||
console.error("Trying to send a message without a valid websocket", messageType, args);
|
||||
return;
|
||||
}
|
||||
this._websocket.send(JSON.stringify([messageType, ...args]));
|
||||
}
|
||||
|
||||
/**
|
||||
* @overload
|
||||
* @param {string|string[]} text The prompt message (the request to get the user to enter some info).
|
||||
* @param {string?} context
|
||||
*/ /**
|
||||
* @overload
|
||||
* @param {string|string[]} text The prompt message (the request to get the user to enter some info).
|
||||
* @param {object?} options Any options for the text (client side text formatting, color-, font-, or style info, etc.).
|
||||
*/
|
||||
sendPrompt(text, options) {
|
||||
options = options || {};
|
||||
|
||||
if (typeof options === "string") {
|
||||
// if options is just a string, assume we meant to apply a context to the prompt
|
||||
options = { context: options };
|
||||
}
|
||||
|
||||
this.send(
|
||||
msg.PROMPT, // message type
|
||||
text, // TODO: prompt text must be string or an array of strings
|
||||
mustBe(options, "object"),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send text to be displayed to the client
|
||||
*
|
||||
* @param {string|string[]} text Text to send. If array, each element will be displayed as its own line on the client side.
|
||||
* @param {object?} options message options for the client.
|
||||
*/
|
||||
sendText(text, options = {}) {
|
||||
this.send(msg.TEXT, text, options);
|
||||
}
|
||||
|
||||
/** @param {string|string[]} errorMessage */
|
||||
sendError(errorMessage) {
|
||||
this.send(msg.ERROR, mustBeString(errorMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a calamity text and then close the connection.
|
||||
* @param {string|string[]} errorMessage Text to send. If array, each element will be displayed as its own line on the client side.
|
||||
*/
|
||||
calamity(errorMessage) {
|
||||
//
|
||||
// The client should know not to format calamaties anyway, but we add “preformatted” anyway
|
||||
this.send(msg.CALAMITY, errorMessage, { preformatted: true });
|
||||
this.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} systemMessageType
|
||||
* @param {any?} value
|
||||
*/
|
||||
sendSystemMessage(systemMessageType, value = undefined) {
|
||||
this.send(msg.SYSTEM, mustBeString(systemMessageType), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user