Stuff
This commit is contained in:
@@ -9,6 +9,7 @@
|
||||
|
||||
import { isIdSane, miniUid } from "../utils/id.js";
|
||||
import { Xorshift32 } from "../utils/random.js";
|
||||
import { Security } from "../utils/security.js";
|
||||
import { ItemBlueprint } from "./item.js";
|
||||
import { Player } from "./player.js";
|
||||
|
||||
@@ -83,18 +84,17 @@ export class Game {
|
||||
* @param {string?} passwordHash
|
||||
* @param {string?} salt
|
||||
*
|
||||
* @returns {Player|null} Returns the player if username wasn't already taken, or null otherwise.
|
||||
* @returns {Player|false} Returns the player if username wasn't already taken, or null otherwise.
|
||||
*/
|
||||
createPlayer(username, passwordHash = undefined, salt = undefined) {
|
||||
if (this.#players.has(username)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const player = new Player(
|
||||
username,
|
||||
typeof passwordHash === "string" ? passwordHash : "",
|
||||
typeof salt === "string" && salt.length > 0 ? salt : miniUid(),
|
||||
);
|
||||
passwordHash ??= "";
|
||||
salt ??= Security.generateHash(miniUid());
|
||||
|
||||
const player = new Player(username, passwordHash, salt);
|
||||
|
||||
this.#players.set(username, player);
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Player } from "./player.js";
|
||||
import { mustBeString, mustBe } from "../utils/mustbe.js";
|
||||
import { Scene } from "../scenes/scene.js";
|
||||
import { formatMessage, MessageType } from "../utils/messages.js";
|
||||
import * as Messages from "../utils/messages.js";
|
||||
|
||||
/** @typedef {import("ws").WebSocket} WebSocket */
|
||||
|
||||
@@ -42,7 +42,7 @@ export class Session {
|
||||
* @param {Scene} scene
|
||||
*/
|
||||
setScene(scene) {
|
||||
console.debug("changing scene", scene.constructor.name);
|
||||
console.debug("Changing scene", { scene: scene.constructor.name });
|
||||
if (!(scene instanceof Scene)) {
|
||||
throw new Error(`Expected instance of Scene, got a ${typeof scene}: >>${scene}<<`);
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export class Session {
|
||||
console.error("Trying to send a message without a valid websocket", messageType, args);
|
||||
return;
|
||||
}
|
||||
this._websocket.send(formatMessage(messageType, ...args));
|
||||
this._websocket.send(Messages.formatMessage(messageType, ...args));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -112,7 +112,7 @@ export class Session {
|
||||
}
|
||||
|
||||
this.send(
|
||||
MessageType.PROMPT, // message type
|
||||
Messages.PROMPT, // message type
|
||||
text, // TODO: prompt text must be string or an array of strings
|
||||
mustBe(options, "object"),
|
||||
);
|
||||
@@ -125,12 +125,17 @@ export class Session {
|
||||
* @param {object?} options message options for the client.
|
||||
*/
|
||||
sendText(text, options = {}) {
|
||||
this.send(MessageType.TEXT, text, options);
|
||||
this.send(Messages.TEXT, text, options);
|
||||
}
|
||||
|
||||
/** @param {string|string[]} errorMessage */
|
||||
sendError(errorMessage, options = { verbatim: true, error: true }) {
|
||||
this.send(MessageType.ERROR, mustBeString(errorMessage), options);
|
||||
this.send(Messages.ERROR, mustBeString(errorMessage), options);
|
||||
}
|
||||
|
||||
/** @param {string|string[]} debugMessage */
|
||||
sendDebug(debugMessage, options = { verbatim: true, debug: true }) {
|
||||
this.send(Messages.DEBUG, debugMessage, options);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -141,7 +146,7 @@ export class Session {
|
||||
//
|
||||
// The client should know not to format calamaties anyway, but we add “preformatted” anyway
|
||||
console.info("CALAMITY", errorMessage);
|
||||
this.send(MessageType.CALAMITY, errorMessage, { verbatim: true, calamity: true });
|
||||
this.send(Messages.CALAMITY, errorMessage, { verbatim: true, calamity: true });
|
||||
this.close();
|
||||
}
|
||||
|
||||
@@ -150,6 +155,6 @@ export class Session {
|
||||
* @param {any?} value
|
||||
*/
|
||||
sendSystemMessage(systemMessageType, value = undefined) {
|
||||
this.send(MessageType.SYSTEM, mustBeString(systemMessageType), value);
|
||||
this.send(Messages.SYSTEM, mustBeString(systemMessageType), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,9 +147,11 @@ class PasswordPrompt extends Prompt {
|
||||
return;
|
||||
}
|
||||
|
||||
const player = this.scene.player;
|
||||
|
||||
//
|
||||
// Block users who enter bad passwords too many times.
|
||||
if (this.player.failedPasswordsSinceLastLogin > Config.maxFailedLogins) {
|
||||
if (player.failedPasswordsSinceLastLogin > Config.maxFailedLogins) {
|
||||
this.blockedUntil = Date.now() + Config.accountLockoutSeconds * 1000;
|
||||
this.calamity("You have been locked out for too many failed password attempts, come back later");
|
||||
return;
|
||||
@@ -158,7 +160,7 @@ class PasswordPrompt extends Prompt {
|
||||
//
|
||||
// Handle blocked users.
|
||||
// They don't even get to have their password verified.
|
||||
if (this.player.blockedUntil > Date.now()) {
|
||||
if (player.blockedUntil > Date.now()) {
|
||||
//
|
||||
// Try to re-login too soon, and your lockout lasts longer.
|
||||
this.blockedUntil += Config.accountLockoutSeconds * 1000;
|
||||
@@ -168,23 +170,23 @@ class PasswordPrompt extends Prompt {
|
||||
|
||||
//
|
||||
// Verify the password against the hash we've stored.
|
||||
if (!Security.verifyPassword(text, this.player.passwordHash)) {
|
||||
if (!Security.verifyPassword(text, player.passwordHash)) {
|
||||
this.sendError("Incorrect password!");
|
||||
this.player.failedPasswordsSinceLastLogin++;
|
||||
player.failedPasswordsSinceLastLogin++;
|
||||
|
||||
this.session.sendDebug(`Failed login attempt #${this.player.failedPasswordsSinceLastLogin}`);
|
||||
this.session.sendDebug(`Failed login attempt #${player.failedPasswordsSinceLastLogin}`);
|
||||
this.execute();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.player.lastSucessfulLoginAt = new Date();
|
||||
this.player.failedPasswordsSinceLastLogin = 0;
|
||||
player.lastSucessfulLoginAt = new Date();
|
||||
player.failedPasswordsSinceLastLogin = 0;
|
||||
|
||||
//
|
||||
// We do not allow a player to be logged in more than once!
|
||||
if (this.player.loggedIn) {
|
||||
this.calamity("This player is already logged in");
|
||||
if (player.loggedIn) {
|
||||
this.calamity("player is already logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,3 @@
|
||||
import figlet from "figlet";
|
||||
import { Session } from "../models/session.js";
|
||||
import { WebsocketMessage } from "../utils/messages.js";
|
||||
import { frameText } from "../utils/tui.js";
|
||||
import { Config } from "../config.js";
|
||||
import { State } from "./state.js";
|
||||
|
||||
// _____ ___ ____ ___ ____ _ _____
|
||||
// |_ _/ _ \| _ \ / _ \ _ / ___|___ _ ____ _____ _ __| |_ |_ _|__
|
||||
// | || | | | | | | | | (_) | | / _ \| '_ \ \ / / _ \ '__| __| | |/ _ \
|
||||
@@ -17,93 +10,93 @@ import { State } from "./state.js";
|
||||
// ___) | (_| __/ | | | __/\__ \
|
||||
// |____/ \___\___|_| |_|\___||___/
|
||||
|
||||
export class PartyCreationState extends State {
|
||||
/**
|
||||
* @proteted
|
||||
* @type {(msg: WebsocketMessage) => }
|
||||
*
|
||||
* NOTE: Should this be a stack?
|
||||
*/
|
||||
_dynamicMessageHandler;
|
||||
|
||||
/** @param {Session} session */
|
||||
constructor(session) {
|
||||
super();
|
||||
this.session = session;
|
||||
}
|
||||
|
||||
/** We attach (and execute) the next state */
|
||||
onAttach() {
|
||||
const charCount = this.session.player.characters.size;
|
||||
|
||||
//NOTE: could use async to optimize performance
|
||||
const createPartyLogo = frameText(figlet.textSync("Create Your Party"), {
|
||||
vPadding: 0,
|
||||
frameChars: "§=§§§§§§",
|
||||
});
|
||||
|
||||
this.sendText(createPartyLogo, { preformatted: true });
|
||||
|
||||
this.session.sendText(["", `Current party size: ${charCount}`, `Max party size: ${Config.maxPartySize}`]);
|
||||
const min = 1;
|
||||
const max = Config.maxPartySize - charCount;
|
||||
const prompt = [
|
||||
`Please enter an integer between ${min} - ${max}`,
|
||||
"((type *:help* to get more info about party size))",
|
||||
];
|
||||
|
||||
this.sendText(`You can create a party with ${min} - ${max} characters, how big should your party be?`);
|
||||
|
||||
/** @param {WebsocketMessage} m */
|
||||
this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
}
|
||||
|
||||
/** @param {WebsocketMessage} m */
|
||||
receiveCharacterCount(m) {
|
||||
if (m.isHelpRequest()) {
|
||||
return this.partySizeHelp();
|
||||
}
|
||||
|
||||
if (!m.isInteger()) {
|
||||
this.sendError("You didn't enter an integer");
|
||||
this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
return;
|
||||
}
|
||||
|
||||
const numCharactersToCreate = Number(m.text);
|
||||
if (numCharactersToCreate > Config.maxPartySize) {
|
||||
this.sendError("Number too high");
|
||||
this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
return;
|
||||
}
|
||||
|
||||
if (numCharactersToCreate < 1) {
|
||||
this.sendError("Number too low");
|
||||
this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
return;
|
||||
}
|
||||
|
||||
this.sendText(`Let's create ${numCharactersToCreate} character(s) for you :)`);
|
||||
}
|
||||
|
||||
partySizeHelp() {
|
||||
this.sendText([
|
||||
`Your party can consist of 1 to ${Config.maxPartySize} characters.`,
|
||||
"",
|
||||
"* Large parties tend live longer",
|
||||
`* If you have fewer than ${Config.maxPartySize} characters, you can`,
|
||||
" hire extra characters in your local inn.",
|
||||
"* large parties level slower because there are more",
|
||||
" characters to share the Experience Points",
|
||||
"* The individual members of small parties get better",
|
||||
" loot because they don't have to share, but it",
|
||||
" a lot of skill to accumulate loot as fast a larger",
|
||||
" party can",
|
||||
]);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (Math.PI < 0 && Session && WebsocketMessage) {
|
||||
("STFU Linda");
|
||||
}
|
||||
// export class PartyCreationState extends State {
|
||||
// /**
|
||||
// * @proteted
|
||||
// * @type {(msg: WebsocketMessage) => }
|
||||
// *
|
||||
// * NOTE: Should this be a stack?
|
||||
// */
|
||||
// _dynamicMessageHandler;
|
||||
//
|
||||
// /** @param {Session} session */
|
||||
// constructor(session) {
|
||||
// super();
|
||||
// this.session = session;
|
||||
// }
|
||||
//
|
||||
// /** We attach (and execute) the next state */
|
||||
// onAttach() {
|
||||
// const charCount = this.session.player.characters.size;
|
||||
//
|
||||
// //NOTE: could use async to optimize performance
|
||||
// const createPartyLogo = frameText(figlet.textSync("Create Your Party"), {
|
||||
// vPadding: 0,
|
||||
// frameChars: "§=§§§§§§",
|
||||
// });
|
||||
//
|
||||
// this.sendText(createPartyLogo, { preformatted: true });
|
||||
//
|
||||
// this.session.sendText(["", `Current party size: ${charCount}`, `Max party size: ${Config.maxPartySize}`]);
|
||||
// const min = 1;
|
||||
// const max = Config.maxPartySize - charCount;
|
||||
// const prompt = [
|
||||
// `Please enter an integer between ${min} - ${max}`,
|
||||
// "((type *:help* to get more info about party size))",
|
||||
// ];
|
||||
//
|
||||
// this.sendText(`You can create a party with ${min} - ${max} characters, how big should your party be?`);
|
||||
//
|
||||
// /** @param {WebsocketMessage} m */
|
||||
// this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
// }
|
||||
//
|
||||
// /** @param {WebsocketMessage} m */
|
||||
// receiveCharacterCount(m) {
|
||||
// if (m.isHelpRequest()) {
|
||||
// return this.partySizeHelp();
|
||||
// }
|
||||
//
|
||||
// if (!m.isInteger()) {
|
||||
// this.sendError("You didn't enter an integer");
|
||||
// this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// const numCharactersToCreate = Number(m.text);
|
||||
// if (numCharactersToCreate > Config.maxPartySize) {
|
||||
// this.sendError("Number too high");
|
||||
// this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// if (numCharactersToCreate < 1) {
|
||||
// this.sendError("Number too low");
|
||||
// this.sendPrompt(prompt, (m) => this.receiveCharacterCount(m));
|
||||
// return;
|
||||
// }
|
||||
//
|
||||
// this.sendText(`Let's create ${numCharactersToCreate} character(s) for you :)`);
|
||||
// }
|
||||
//
|
||||
// partySizeHelp() {
|
||||
// this.sendText([
|
||||
// `Your party can consist of 1 to ${Config.maxPartySize} characters.`,
|
||||
// "",
|
||||
// "* Large parties tend live longer",
|
||||
// `* If you have fewer than ${Config.maxPartySize} characters, you can`,
|
||||
// " hire extra characters in your local inn.",
|
||||
// "* large parties level slower because there are more",
|
||||
// " characters to share the Experience Points",
|
||||
// "* The individual members of small parties get better",
|
||||
// " loot because they don't have to share, but it",
|
||||
// " a lot of skill to accumulate loot as fast a larger",
|
||||
// " party can",
|
||||
// ]);
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (Math.PI < 0 && Session && WebsocketMessage) {
|
||||
// ("STFU Linda");
|
||||
// }
|
||||
|
||||
@@ -1,75 +0,0 @@
|
||||
import { Prompt } from "../prompt.js";
|
||||
import { Security } from "../../utils/security.js";
|
||||
import { Config } from "../../config.js";
|
||||
|
||||
export class CreatePasswordPrompt extends Prompt {
|
||||
//
|
||||
message = ["Enter a password"];
|
||||
|
||||
//
|
||||
// Let the client know that we're asking for a password
|
||||
// so it can set <input type="password">
|
||||
options = { password: true };
|
||||
|
||||
get player() {
|
||||
return this.scene.player;
|
||||
}
|
||||
|
||||
onReply(text) {
|
||||
//
|
||||
// Check of the password is sane. This is both bad from a security point
|
||||
// of view, and technically not necessary as insane passwords couldn't
|
||||
// reside in the player lists. However, let's save some CPU cycles on
|
||||
// not hashing an insane password 1000+ times.
|
||||
// This is technically bad practice, but since this is just a game,
|
||||
// do it anyway.
|
||||
if (!Security.isPasswordSane(text)) {
|
||||
this.sendError("Insane password");
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Block users who enter bad passwords too many times.
|
||||
if (this.player.failedPasswordsSinceLastLogin > Config.maxFailedLogins) {
|
||||
this.blockedUntil = Date.now() + Config.accountLockoutSeconds;
|
||||
this.calamity("You have been locked out for too many failed password attempts, come back later");
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Handle blocked users.
|
||||
// They don't even get to have their password verified.
|
||||
if (this.player.blockedUntil > Date.now()) {
|
||||
//
|
||||
// Try to re-login too soon, and your lockout lasts longer.
|
||||
this.blockedUntil += Config.accountLockoutSeconds;
|
||||
this.calamity("You have been locked out for too many failed password attempts, come back later");
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Verify the password against the hash we've stored.
|
||||
if (!Security.verifyPassword(text, this.player.passwordHash)) {
|
||||
this.sendError("Incorrect password!");
|
||||
this.player.failedPasswordsSinceLastLogin++;
|
||||
|
||||
this.session.sendDebug(`Failed login attempt #${this.player.failedPasswordsSinceLastLogin}`);
|
||||
this.execute();
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.player.lastSucessfulLoginAt = new Date();
|
||||
this.player.failedPasswordsSinceLastLogin = 0;
|
||||
|
||||
//
|
||||
// We do not allow a player to be logged in more than once!
|
||||
if (this.player.loggedIn) {
|
||||
this.calamity("This player is already logged in");
|
||||
return;
|
||||
}
|
||||
|
||||
this.scene.passwordAccepted();
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import { Prompt } from "../prompt.js";
|
||||
import { Security } from "../../utils/security.js";
|
||||
import { gGame } from "../../models/globals.js";
|
||||
|
||||
/** @typedef {import("./playerCreationScene.js").PlayerCreationScene} PlayerCreationScene */
|
||||
|
||||
export class CreateUsernamePrompt extends Prompt {
|
||||
//
|
||||
message = [
|
||||
"Enter your username", //
|
||||
"((type *:help* for more info))", //
|
||||
];
|
||||
|
||||
//
|
||||
// When player types :help
|
||||
help = [
|
||||
"Your username.",
|
||||
"It's used, along with your password, when you log in.",
|
||||
"Other players can see it.",
|
||||
"Other players can use it to chat or trade with you",
|
||||
"It may only consist of the letters _a-z_, _A-Z_, _0-9_, and _underscore_",
|
||||
];
|
||||
|
||||
//
|
||||
// Let the client know that we're asking for a username
|
||||
options = { username: true };
|
||||
|
||||
/** @protected @type {PlayerCreationScene} */
|
||||
_scene;
|
||||
|
||||
onReply(username) {
|
||||
//
|
||||
// do basic syntax checks on usernames
|
||||
if (!Security.isUsernameSane(username)) {
|
||||
console.info("Someone entered insane username: '%s'", username);
|
||||
this.sendError("Incorrect username, try again.");
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// try and fetch the player object from the game
|
||||
const player = gGame.getPlayerByUsername(username);
|
||||
|
||||
//
|
||||
// handle invalid username
|
||||
if (player) {
|
||||
console.info("Someone tried to create a user with an occupied username: '%s'", username);
|
||||
this.sendError("Occupied, try something else");
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// Tell owner that we're done
|
||||
this._scene.usernameAccepted(username);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,11 @@
|
||||
import { Config } from "../../config.js";
|
||||
import { gGame } from "../../models/globals.js";
|
||||
import { Security } from "../../utils/security.js";
|
||||
import { Prompt } from "../prompt.js";
|
||||
import { Scene } from "../scene.js";
|
||||
import { CreateUsernamePrompt } from "./createUsernamePrompt.js";
|
||||
import { Security } from "../../utils/security.js";
|
||||
import { gGame } from "../../models/globals.js";
|
||||
import { AuthenticationScene } from "../authentication/authenticationScene.js";
|
||||
|
||||
const MAX_PASSWORD_ATTEMPTS = 3;
|
||||
|
||||
export class PlayerCreationScene extends Scene {
|
||||
intro = "= Create Player";
|
||||
@@ -20,7 +23,7 @@ export class PlayerCreationScene extends Scene {
|
||||
this.session.calamity("Server is full, no more players can be created");
|
||||
}
|
||||
|
||||
this.showPrompt(new CreateUsernamePrompt(this));
|
||||
this.showPrompt(new UsernamePrompt(this));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -33,10 +36,10 @@ export class PlayerCreationScene extends Scene {
|
||||
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.");
|
||||
this.session.sendText(`Username _*${username}*_ has been reserved for you`);
|
||||
|
||||
this.show(PasswordPrompt);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -47,7 +50,153 @@ export class PlayerCreationScene extends Scene {
|
||||
*/
|
||||
passwordAccepted(password) {
|
||||
this.password = password;
|
||||
this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry for that ;)");
|
||||
this.player.setPasswordHash(Security.generateHash(this.password));
|
||||
this.session.sendText("*_Success_* ✅ You will now be asked to log in again, sorry about that ;)");
|
||||
this.session.setScene(new AuthenticationScene(this.session));
|
||||
}
|
||||
}
|
||||
|
||||
// _ _
|
||||
// | | | |___ ___ _ __ _ __ __ _ _ __ ___ ___
|
||||
// | | | / __|/ _ \ '__| '_ \ / _` | '_ ` _ \ / _ \
|
||||
// | |_| \__ \ __/ | | | | | (_| | | | | | | __/
|
||||
// \___/|___/\___|_| |_| |_|\__,_|_| |_| |_|\___|
|
||||
//
|
||||
// ____ _
|
||||
// | _ \ _ __ ___ _ __ ___ _ __ | |_
|
||||
// | |_) | '__/ _ \| '_ ` _ \| '_ \| __|
|
||||
// | __/| | | (_) | | | | | | |_) | |_
|
||||
// |_| |_| \___/|_| |_| |_| .__/ \__|
|
||||
// |_|
|
||||
class UsernamePrompt extends Prompt {
|
||||
//
|
||||
message = [
|
||||
"Enter your username", //
|
||||
"((type *:help* for more info))", //
|
||||
];
|
||||
|
||||
//
|
||||
// When player types :help
|
||||
help = [
|
||||
"Your username.",
|
||||
"It's used, along with your password, when you log in.",
|
||||
"Other players can see it.",
|
||||
"Other players can use it to chat or trade with you",
|
||||
"It may only consist of the letters _a-z_, _A-Z_, _0-9_, and _underscore_",
|
||||
];
|
||||
|
||||
//
|
||||
// Let the client know that we're asking for a username
|
||||
options = { username: true };
|
||||
|
||||
/** @type {PlayerCreationScene} */
|
||||
get scene() {
|
||||
return this._scene;
|
||||
}
|
||||
|
||||
onReply(username) {
|
||||
//
|
||||
// do basic syntax checks on usernames
|
||||
if (!Security.isUsernameSane(username)) {
|
||||
console.info("Someone entered insane username: '%s'", username);
|
||||
this.sendError("Incorrect username, try again.");
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
// try and fetch the player object from the game
|
||||
const player = gGame.getPlayerByUsername(username);
|
||||
|
||||
//
|
||||
// handle invalid username
|
||||
if (player) {
|
||||
console.info("Someone tried to create a user with an occupied username: '%s'", username);
|
||||
this.sendError("Occupied, try something else");
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
this.scene.usernameAccepted(username);
|
||||
}
|
||||
}
|
||||
|
||||
// ____ _
|
||||
// | _ \ __ _ ___ _____ _____ _ __ __| |
|
||||
// | |_) / _` / __/ __\ \ /\ / / _ \| '__/ _` |
|
||||
// | __/ (_| \__ \__ \\ V V / (_) | | | (_| |
|
||||
// |_| \__,_|___/___/ \_/\_/ \___/|_| \__,_|
|
||||
//
|
||||
// ____ _
|
||||
// | _ \ _ __ ___ _ __ ___ _ __ | |_
|
||||
// | |_) | '__/ _ \| '_ ` _ \| '_ \| __|
|
||||
// | __/| | | (_) | | | | | | |_) | |_
|
||||
// |_| |_| \___/|_| |_| |_| .__/ \__|
|
||||
// |_|
|
||||
class PasswordPrompt extends Prompt {
|
||||
//
|
||||
message = "Enter a password";
|
||||
|
||||
//
|
||||
// Let the client know that we're asking for a password
|
||||
// so it can set <input type="password">
|
||||
options = { password: true };
|
||||
|
||||
/** @type {string?} Password that was previously entered. */
|
||||
firstPassword = undefined;
|
||||
|
||||
errorCount = 0;
|
||||
|
||||
/** @type {PlayerCreationScene} */
|
||||
get scene() {
|
||||
return this._scene;
|
||||
}
|
||||
|
||||
beforeExecute() {
|
||||
if (this.errorCount > MAX_PASSWORD_ATTEMPTS) {
|
||||
this.firstPassword = false;
|
||||
this.errorCount = 0;
|
||||
this.message = ["Too many errors - starting over", "Enter password"];
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.firstPassword && this.errorCount === 0) {
|
||||
this.message = "Repeat the password";
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.firstPassword && this.errorCount > 0) {
|
||||
this.message = [
|
||||
"Repeat the password",
|
||||
`((attempt nr. ${this.errorCount + 1} of ${MAX_PASSWORD_ATTEMPTS + 1}))`,
|
||||
];
|
||||
return;
|
||||
}
|
||||
|
||||
this.errorCount = 0;
|
||||
this.message = "Enter a password";
|
||||
}
|
||||
|
||||
onReply(str) {
|
||||
if (!Security.isPasswordSane(str)) {
|
||||
this.sendError("Invalid password format.");
|
||||
this.errorCount++;
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.firstPassword) {
|
||||
this.firstPassword = str;
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.firstPassword !== str) {
|
||||
this.errorCount++;
|
||||
this.execute();
|
||||
return;
|
||||
}
|
||||
|
||||
this.scene.passwordAccepted(str);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
/** @typedef {import("../models/session.js").Session} Session */
|
||||
/** @typedef {import("../utils/message.js").MessageType} MessageType */
|
||||
/** @typedef {import("../utils/message.js").WebsocketMessage} WebsocketMessage */
|
||||
/** @typedef {import("./scene.js").Scene} Scene */
|
||||
|
||||
@@ -65,7 +64,7 @@ export class Prompt {
|
||||
|
||||
/** @type {Session} */
|
||||
get session() {
|
||||
return this.scene.session;
|
||||
return this._scene.session;
|
||||
}
|
||||
|
||||
/** @param {Scene} scene */
|
||||
@@ -80,6 +79,7 @@ export class Prompt {
|
||||
*/
|
||||
execute() {
|
||||
this.prepareProperties();
|
||||
this.beforeExecute();
|
||||
|
||||
this.sendPrompt(this.message, this.options);
|
||||
}
|
||||
@@ -98,6 +98,8 @@ export class Prompt {
|
||||
}
|
||||
}
|
||||
|
||||
beforeExecute() {}
|
||||
|
||||
/** Triggered when user types `:help [some optional topic]` */
|
||||
onHelp(topic) {
|
||||
if (!this.help) {
|
||||
|
||||
Reference in New Issue
Block a user