diff --git a/eslint.config.js b/eslint.config.js index 5120f65..4c04670 100755 --- a/eslint.config.js +++ b/eslint.config.js @@ -9,6 +9,9 @@ export default defineConfig([ plugins: { js }, // crlf extends: ["js/recommended"], // crlf languageOptions: { globals: globals.browser }, + rules: { + "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + }, }, { // Config starts here @@ -16,5 +19,8 @@ export default defineConfig([ plugins: { js }, // crlf extends: ["js/recommended"], // crlf languageOptions: { globals: globals.node }, + rules: { + "no-unused-vars": ["error", { argsIgnorePattern: "^_" }], + }, }, ]); diff --git a/scenes/authentication/authenticationScene.js b/scenes/authentication/authenticationScene.js index ee184ce..aa3969c 100755 --- a/scenes/authentication/authenticationScene.js +++ b/scenes/authentication/authenticationScene.js @@ -1,8 +1,10 @@ import { PasswordPrompt } from "./passwordPrompt.js"; -import { Player } from "../../models/player.js"; import { Scene } from "../scene.js"; import { UsernamePrompt } from "./usernamePrompt.js"; import { PlayerCreationScene } from "../playerCreation/playerCreationSene.js"; +import { GameScene } from "../gameLoop/gameScene.js"; + +/** @typedef {import("../../models/player.js").Player} Player */ /** @property {Session} session */ export class AuthenticationScene extends Scene { @@ -31,7 +33,7 @@ export class AuthenticationScene extends Scene { this.session.sendText(["= Success!", "((but I don't know what to do now...))"]); - this.session.setScene("new JustLoggedInScene"); + this.session.setScene(new GameScene()); } /** diff --git a/scenes/authentication/passwordPrompt.js b/scenes/authentication/passwordPrompt.js index d2adce3..511bad8 100755 --- a/scenes/authentication/passwordPrompt.js +++ b/scenes/authentication/passwordPrompt.js @@ -1,7 +1,8 @@ import { Prompt } from "../prompt.js"; import * as security from "../../utils/security.js"; import { Config } from "../../config.js"; -import { AuthenticationScene } from "./authenticationScene.js"; + +/** @typedef {import("./authentication.js").AuthenticationScene} AuthenticationScene */ export class PasswordPrompt extends Prompt { // diff --git a/scenes/authentication/usernamePrompt.js b/scenes/authentication/usernamePrompt.js index 2cc96c8..123deb4 100755 --- a/scenes/authentication/usernamePrompt.js +++ b/scenes/authentication/usernamePrompt.js @@ -1,15 +1,10 @@ -import { Player } from "../../models/player.js"; import { Prompt } from "../prompt.js"; -import * as security from "../../utils/security.js"; import { gGame } from "../../models/globals.js"; -import { Config } from "../../config.js"; -import { AuthenticationScene } from "./authenticationScene.js"; +import * as security from "../../utils/security.js"; + +/** @typedef {import("./authenticationScene.js").AuthenticationScene} AuthenticationScene */ +/** @typedef {import("../../models/player.js").Player} Player */ -/** - * @class - * - * @property {AuthenticationScene} scene - */ export class UsernamePrompt extends Prompt { // promptText = [ diff --git a/scenes/gameLoop/gameScene.js b/scenes/gameLoop/gameScene.js index f120dad..972d617 100755 --- a/scenes/gameLoop/gameScene.js +++ b/scenes/gameLoop/gameScene.js @@ -14,6 +14,19 @@ export class GameScene extends Scene { // Find out where we are // Re-route to the relevant scene if necessary. // + // IF player has stored state THEN + // restore it and resume [main flow] + // END + // + // IF player has no characters THEN + // go to createCharacterScene + // END + // + // set player's current location = Hovedstad + // display the welcome to Hovedstad stuff, and + // await the player's commands. + // + // // IDEA: // Does a player have a previous state? // The state that was on the previous session? @@ -21,6 +34,50 @@ export class GameScene extends Scene { // If player does not have a previous session // then we start in the Adventurers Guild in the Hovedstad // - this.doPrompt("new commandprompt or whatever"); + this.doPrompt("new command prompt or whatever"); + } + + get castle() { + return ` + ▄ + █▐▀▀▀▌▄ + █ ▐▀▀▀▌▌▓▌ + █ ▄▄ ▄▄▀ + █ ▐▀▀▀▀ + ▄█▄ + ▓▀ ▀▌ + ▓▀ ▓▄ + ▄▓ ▐▓ + ▄▓ ▀▌ + ▓▀▀▀▀▀▓ ▓▀▀▀▀▓ ▐█▀▀▀▀▓ + █ █ █ ▓░ ▓▌ ▓░ + █ ▀▀▀▀▀ ▀▀▀▀▀ ▓░ + ▓▒ ▓░ + ▀▓▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ + ▐▌ █ + ▓▀▀▀▀█ ▐█▀▀▀█ ▐█▀▀▀▓▒ ▐▌ █ ▐▓▀▀▀▓▒ ▓▀▀▀▓▒ █▀▀▀▀▓ + █ █ ▐▌ █ ▐▌ ▓▒ ▐▌ ▐██░ █ ▐█ ▓▄ █ ▐▌ █ ▐█ + ▓░ ▐▀▀▀ ▐▀▀▀ █░ ▐▌ ▓██▌ █ ▐█ ▀▀▀▀ ▀▀▀ ▐▌ + ▓▒ █ ▐▌ ▀██▌ █ █ ▐▌ + ▀▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▓▀ ▐▌ █ ▀▌▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▓ + ▐▌ █ ▐▌ █ █ ▐▌ + ▐▌ █ ▐▌ █ █ ▐▌ + ▐▌ ▓▌ █▀▀▀▀▀█ ▐▌▐▌▀▀▀▀█ ▓▀▀▀▀▓▄ █ █▀▀▀▀▀█ ▓▌ ▐▌ + ▓▌ ██▌ █ █ ▓▌▓▒ █ █ ▐▌ █ █ █ ▓██ ▐▓ + ▓▒ ▐██▌ █ █ ▓██░ ▐█ + ▓ ▐▐ █ █ ▐▐ █ + █ █ █ █ + █ █ ▄▄▄ █ █ + █ █ ▄▀▀ ▀▀▓▄ █ █ + █ █ ▄▌ ▀▓ █ █ + ▐█ █ ▓▀ ▐█ █ ▓▒ + ▐▌ █ ▐▓ ▐▌ ▐█ ▓▒ + ▐▌ █ █ █ ▐█ ▐▌ + ▐▌ ▓░ █ █ ▐▌ ▐▌ + ▓▒ ▓░ █ ▓▒ ▐▌ ▐▓ + ▓░ ▓░ ▐▌ ▀▌ ▐▌ ▐█ + ▀▌▄▄ ▓▄▄ ▐█ ▓▌ ▄▄▄▐▌ ▄▄▄▀ + ▐▐▐▀▀▀▀▐▐▐ ▐▐▀▀▀▀▀▀▐▐ +`; } } diff --git a/scenes/interface.js b/scenes/interface.js index eae3ebb..d08e02d 100755 --- a/scenes/interface.js +++ b/scenes/interface.js @@ -1,13 +1,13 @@ -import { WebsocketMessage } from "../utils/messages.js"; -import { Session } from "../models/session.js"; +/** @typedef {import("../models/session.js").Session} Session */ +/** @typedef {import("../util/messages.js").WebsocketMessage} WebsocketMessage */ /** @interface */ export class StateInterface { /** @param {Session} session */ - constructor(session) {} + constructor(_session) {} onAttach() {} /** @param {WebsocketMessage} message */ - onMessage(message) {} + onMessage(_message) {} } diff --git a/scenes/justLoggedIn/justLoggedInScene.js b/scenes/justLoggedIn/justLoggedInScene.js deleted file mode 100755 index 7351c07..0000000 --- a/scenes/justLoggedIn/justLoggedInScene.js +++ /dev/null @@ -1,72 +0,0 @@ -/** @typedef {import("../models/session.js").Session} Session */ - -const castle = ` - ▄ - █▐▀▀▀▌▄ - █ ▐▀▀▀▌▌▓▌ - █ ▄▄ ▄▄▀ - █ ▐▀▀▀▀ - ▄█▄ - ▓▀ ▀▌ - ▓▀ ▓▄ - ▄▓ ▐▓ - ▄▓ ▀▌ - ▓▀▀▀▀▀▓ ▓▀▀▀▀▓ ▐█▀▀▀▀▓ - █ █ █ ▓░ ▓▌ ▓░ - █ ▀▀▀▀▀ ▀▀▀▀▀ ▓░ - ▓▒ ▓░ - ▀▓▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄█ - ▐▌ █ - ▓▀▀▀▀█ ▐█▀▀▀█ ▐█▀▀▀▓▒ ▐▌ █ ▐▓▀▀▀▓▒ ▓▀▀▀▓▒ █▀▀▀▀▓ - █ █ ▐▌ █ ▐▌ ▓▒ ▐▌ ▐██░ █ ▐█ ▓▄ █ ▐▌ █ ▐█ - ▓░ ▐▀▀▀ ▐▀▀▀ █░ ▐▌ ▓██▌ █ ▐█ ▀▀▀▀ ▀▀▀ ▐▌ - ▓▒ █ ▐▌ ▀██▌ █ █ ▐▌ - ▀▀▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▓▀ ▐▌ █ ▀▌▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▓ - ▐▌ █ ▐▌ █ █ ▐▌ - ▐▌ █ ▐▌ █ █ ▐▌ - ▐▌ ▓▌ █▀▀▀▀▀█ ▐▌▐▌▀▀▀▀█ ▓▀▀▀▀▓▄ █ █▀▀▀▀▀█ ▓▌ ▐▌ - ▓▌ ██▌ █ █ ▓▌▓▒ █ █ ▐▌ █ █ █ ▓██ ▐▓ - ▓▒ ▐██▌ █ █ ▓██░ ▐█ - ▓ ▐▐ █ █ ▐▐ █ - █ █ █ █ - █ █ ▄▄▄ █ █ - █ █ ▄▀▀ ▀▀▓▄ █ █ - █ █ ▄▌ ▀▓ █ █ - ▐█ █ ▓▀ ▐█ █ ▓▒ - ▐▌ █ ▐▓ ▐▌ ▐█ ▓▒ - ▐▌ █ █ █ ▐█ ▐▌ - ▐▌ ▓░ █ █ ▐▌ ▐▌ - ▓▒ ▓░ █ ▓▒ ▐▌ ▐▓ - ▓░ ▓░ ▐▌ ▀▌ ▐▌ ▐█ - ▀▌▄▄ ▓▄▄ ▐█ ▓▌ ▄▄▄▐▌ ▄▄▄▀ - ▐▐▐▀▀▀▀▐▐▐ ▐▐▀▀▀▀▀▀▐▐ -`; - -/** @interface */ -export class JustLoggedInState { - /** @param {Session} session */ - constructor(session) { - /** @type {Session} */ - this.session = session; - } - - // Show welcome screen - onAttach() { - this.session.sendText(castle); - this.session.sendText(["", "Welcome", "", "You can type “:quit” at any time to quit the game", ""]); - - // - // Check if we need to create characters for the player - if (this.session.player.characters.size === 0) { - this.session.sendText("You haven't got any characters, so let's make some\n\n"); - this.session.setState(new PartyCreationState(this.session)); - return; - } - - for (const character of this.session.player.characters) { - this.session.sendText(`Character: ${character.name} (${character.foundation})`); - } - - this.session.setState(new AwaitCommandsState(this.session)); - } -} diff --git a/scenes/playerCreation/createUasswprdPrompt.js b/scenes/playerCreation/createUasswprdPrompt.js index d72ab6e..1fee7ce 100755 --- a/scenes/playerCreation/createUasswprdPrompt.js +++ b/scenes/playerCreation/createUasswprdPrompt.js @@ -71,9 +71,5 @@ export class CreatePasswordPrompt extends Prompt { } this.scene.passwordAccepted(); - - // - // Password was correct, go to main game - this.session.setState(new JustLoggedInState(this.session)); } } diff --git a/scenes/playerCreation/createUsernamePrompt.js b/scenes/playerCreation/createUsernamePrompt.js index 1578810..ae4f18b 100755 --- a/scenes/playerCreation/createUsernamePrompt.js +++ b/scenes/playerCreation/createUsernamePrompt.js @@ -1,8 +1,8 @@ import { Prompt } from "../prompt.js"; import * as security from "../../utils/security.js"; import { gGame } from "../../models/globals.js"; -import { PlayerCreationScene } from "./playerCreationSene.js"; -import { Config } from "../../config.js"; + +/** @typedef {import("./playerCreationScene.js").PlayerCreationScene} PlayerCreationScene */ export class CreateUsernamePrompt extends Prompt { // diff --git a/scenes/playerCreation/playerCreationScene.js b/scenes/playerCreation/playerCreationScene.js new file mode 100644 index 0000000..8f69fcd --- /dev/null +++ b/scenes/playerCreation/playerCreationScene.js @@ -0,0 +1,53 @@ +import { Config } from "../../config.js"; +import { gGame } from "../../models/globals.js"; +import { generateHash } from "../../utils/security.js"; +import { Scene } from "../scene.js"; +import { CreateUsernamePrompt } from "./createUsernamePrompt.js"; + +export class PlayerCreationScene extends Scene { + introText = "= Create Player"; + + /** @protected @type {Player} */ + player; + + /** @protected @type {string} */ + password; + + onReady() { + // + // If there are too many players, stop allowing new players in. + if (gGame._players.size >= Config.maxPlayers) { + this.session.calamity("Server is full, no more players can be created"); + } + + this.showPrompt(new CreateUsernamePrompt(this)); + } + + /** + * Called when the player has entered a valid and available username. + * + * @param {string} username + */ + usernameAccepted(username) { + const player = gGame.createPlayer(username); + this.player = player; + + this.session.sendSystemMessage("salt", player.salt); + this.session.sendText(`Username _*${username}*_ is available, and I've reserved it for you :)`); + + // + this.session.sendError("TODO: create a createPasswordPrompt and display it."); + } + + /** + * + * Called when the player has entered a password and confirmed it. + * + * @param {string} password + */ + passwordAccepted(password) { + this.password = password; + this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry for that ;)"); + this.player.setPasswordHash(generateHash(this.password)); + } +} diff --git a/scenes/prompt.js b/scenes/prompt.js index 60cce15..d2dcdb7 100755 --- a/scenes/prompt.js +++ b/scenes/prompt.js @@ -1,8 +1,9 @@ import { Scene } from "./scene.js"; /** @typedef {import("../models/session.js").Session} Session */ -/** @typedef {import("../utils/message.js").WebsocketMessage} WebsocketMessage */ /** @typedef {import("../utils/message.js").MessageType} MessageType */ +/** @typedef {import("../utils/message.js").WebsocketMessage} WebsocketMessage */ + /** * @typedef {object} PromptMethods * @property {function(...any): any} [onColon_*] - Any method starting with "onColon_" @@ -16,7 +17,7 @@ import { Scene } from "./scene.js"; * - onColon(...) */ export class Prompt { - /** @private @readonly @type {Scene} */ + /** @type {Scene} */ _scene; /** @type {Scene} */ @@ -29,9 +30,13 @@ export class Prompt { * Keys: string matching /^[a-z]+$/ (topic name) * Values: string containing the help text * + * If you want truly custom help texts, you must override the onHelpFallback function, + * but overriding the onHelp() function gives you more control and skips this helpText + * dictionary entirely. + * * @constant * @readonly - * @type {Record} + * @type {Record} */ helpText = { "": "Sorry, no help available. Figure it out yourself, adventurer", // default help text @@ -43,7 +48,15 @@ export class Prompt { "((or type :quit to run away))", // strings in double parentheses is rendered shaded/faintly ]; - /** @type {object|string} If string: the prompt's context (username, password, etc) of object, it's all the message's options */ + /** @type {object|string} If string: the prompt's context (username, password, etc). If object: it's all the message's options */ + /* @example + * + * // if the prompt expects a username + * promptOptions = { username : true }; + * + * // if the prompt expects a password + * promptOptions = { password : true }; + */ promptOptions = {}; /** @type {Session} */ @@ -57,6 +70,13 @@ export class Prompt { throw new Error("Expected an instance of >>Scene<< but got " + typeof scene); } this._scene = scene; + + // + // Fix data formatting shorthand + // So lazy dev set property helpText = "fooo" instead of helpText = { "": "fooo" }. + if (typeof this.helpText === "string" || Array.isArray(this.helpText)) { + this.helpText = { "": this.helpText }; + } } /** @@ -68,18 +88,10 @@ export class Prompt { this.sendPrompt(this.promptText, this.promptOptions); } - /** Triggered when user types `:help` without any topic */ + /** Triggered when user types `:help [some optional topic]` */ onHelp(topic) { - let h = this.helpText; - if (typeof h === "string" || Array.isArray(h)) { - h = { "": h }; - } - - // - // Fix data formatting shorthand - // So lazy dev set help = "fooo" instead of help = { "": "fooo" }. - if (h[topic]) { - this.sendText(h[topic]); + if (this.helpText[topic]) { + this.sendText(this.helpText[topic]); return; } @@ -129,7 +141,7 @@ export class Prompt { } /** - * Triggered when the player asks for help on a topic, and we dont have an onHelp_thatParticularTopic method. + * Triggered when the player asks for help on a topic, and we don't have an onHelp_thatParticularTopic method. * * @param {string} topic */ diff --git a/utils/tileOptionsParser.js b/utils/tileOptionsParser.js index 3c86de0..456b4ab 100755 --- a/utils/tileOptionsParser.js +++ b/utils/tileOptionsParser.js @@ -86,15 +86,16 @@ export class TileArgs { * @returns {TileOptions[]} * * @example - * // returns + * + * parse(`O(1); P(orientation=north); E(Gnolls, texture=gnolls)`) + * + * // the string above will be parsed into: * // [ * // {name="O", args=[{ key: 0, value: 1 }]}, * // {name="P", args=[{ key: "orientation", value: "north" }]}, * // {name="E", args=[{ key: 0, value: "Gnolls" }, { key: "texture", value: "gnolls" }]}, * // ]; * - * parse(`O(1); P(orientation=north); E(Gnolls, texture=gnolls)`) - * */ export default function parse(input) { const calls = [];