diff --git a/server/models/character.js b/server/models/character.js index a86c9d5..fa6544e 100755 --- a/server/models/character.js +++ b/server/models/character.js @@ -173,8 +173,14 @@ export class Character { 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: // case 2: + 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"); diff --git a/server/models/game.js b/server/models/game.js index 1469725..1702fe3 100755 --- a/server/models/game.js +++ b/server/models/game.js @@ -30,12 +30,41 @@ export class Game { /** * All players ever registered, mapped by name => player. + * + * _____ _ + * | ___(_)_ ___ __ ___ ___ + * | |_ | \ \/ / '_ ` _ \ / _ \ + * | _| | |> <| | | | | | __/ + * |_| |_/_/\_\_| |_| |_|\___| + * + * 1. Add mutex on the players table to avoid race conditions during + * insert/delete/check_available_username + * 1.a ) add an "atomicInsert" that inserts a new player if the giver username + * is available. + * 2. Prune "dead" players (players with 0 logins) after a short while + * * * @protected * @type {Map} Map of users in the game username->Player */ _players = new Map(); - get players() { - return this._players; + + hasPlayer(username) { + return this._players.has(username); + } + + getPlayer(username) { + return this._players.get(username); + } + + createPlayer(username, passwordHash=null) { + if (this._players.has(username)) { + return false; + } + + const player = new Player(username, passwordHash); + this._players.set(username, player); + + return player; } } diff --git a/server/models/item.js b/server/models/item.js index 2f9360d..89aceed 100755 --- a/server/models/item.js +++ b/server/models/item.js @@ -69,6 +69,13 @@ export class ItemTemplate { createItem() { return new ChracterItem(this._id, this._name, this._description, this._itemSlots); } + + static getOrCreate(id, name, description, itemSlots) { + } + + static seed() { + this + } } /** diff --git a/server/models/player.js b/server/models/player.js index 07fca57..c70b79c 100755 --- a/server/models/player.js +++ b/server/models/player.js @@ -25,6 +25,21 @@ export class Player { return this._passwordHash; } + /** @type {Date} */ + _createdAt = new Date(); + + /** @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} */ _characters = new Set(); get characters() { @@ -39,7 +54,7 @@ export class Player { this._username = username; this._passwordHash = passwordHash; - this.createdAt = new Date(); + this._createdAt = new Date(); } setPasswordHash(hashedPassword) { diff --git a/server/models/session.js b/server/models/session.js index cc2d6de..802ff11 100755 --- a/server/models/session.js +++ b/server/models/session.js @@ -19,9 +19,6 @@ export class Session { return this._game; } - /** @type Date */ - latestPing; - /** @type {Player} */ player; diff --git a/server/models/states/auth.js b/server/models/states/auth.js index ed15d3e..87bc1fe 100755 --- a/server/models/states/auth.js +++ b/server/models/states/auth.js @@ -1,8 +1,8 @@ -import { Session } from "../session.js"; import * as msg from "../../utils/messages.js"; import * as security from "../../utils/security.js"; -import { JustLoggedInState } from "./justLoggedIn.js"; import { CreatePlayerState } from "./createPlayer.js"; +import { JustLoggedInState } from "./justLoggedIn.js"; +import { Session } from "../session.js"; const STATE_EXPECT_USERNAME = "promptUsername"; const STATE_EXPECT_PASSWORD = "promptPassword"; @@ -43,7 +43,7 @@ export class AuthState { } if (this.subState === STATE_EXPECT_PASSWORD) { - this.receivePassword(message) + this.receivePassword(message); return; } @@ -81,11 +81,7 @@ export class AuthState { return; } - if (this.session.game.players.size === 0) { - console.error("there are no players registered"); - } - - const player = this.session.game.players.get(message.username); + const player = this.session.game.getPlayer(message.username); // // handle invalid username @@ -142,9 +138,13 @@ export class AuthState { if (!security.verifyPassword(message.password, this.session.player.passwordHash)) { this.session.sendError("Incorrect password!"); this.session.sendPrompt("password", PASSWORD_PROMPT); + this.session.player.failedPasswordsSinceLastLogin++; return; } + this.session.player.lastSucessfulLoginAt = new Date(); + this.session.player.failedPasswordsSinceLastLogin = 0; + // // Password correct, check if player is an admin if (this.session.player.isAdmin) { diff --git a/server/models/states/awaitCommands.js b/server/models/states/awaitCommands.js old mode 100644 new mode 100755 diff --git a/server/models/states/characterCreation.js b/server/models/states/characterCreation.js index a5f8354..12e45c5 100755 --- a/server/models/states/characterCreation.js +++ b/server/models/states/characterCreation.js @@ -1,7 +1,5 @@ import figlet from "figlet"; import { Session } from "../session.js"; -import WebSocket from "ws"; -import { AuthState } from "./auth.js"; import { ClientMessage } from "../../utils/messages.js"; import { PARTY_MAX_SIZE } from "../../config.js"; import { frameText } from "../../utils/tui.js"; diff --git a/server/models/states/createPlayer.js b/server/models/states/createPlayer.js index 3c70aa2..b49f44b 100755 --- a/server/models/states/createPlayer.js +++ b/server/models/states/createPlayer.js @@ -1,4 +1,3 @@ -import WebSocket from "ws"; import { Session } from "../session.js"; import * as msg from "../../utils/messages.js"; import * as security from "../../utils/security.js"; @@ -79,11 +78,11 @@ export class CreatePlayerState { return; } - const taken = this.session.game.players.has(message.username); + const player = this.session.game.createPlayer(message.username); // // handle taken/occupied username - if (taken) { + if (player === false) { // Telling the user right away that the username is taken can // lead to data leeching. But fukkit. @@ -92,18 +91,9 @@ export class CreatePlayerState { return; } - this._player = new Player(message.username, undefined); + this._player = player; - // _____ _ - // | ___(_)_ ___ __ ___ ___ - // | |_ | \ \/ / '_ ` _ \ / _ \ - // | _| | |> <| | | | | | __/ - // |_| |_/_/\_\_| |_| |_|\___| - // - // 1. Add mutex on the players table to avoid race conditions - // 2. Prune "dead" players (players with 0 logins) after a short while - this.session.game.players.set(message.username, this._player); - this.session.sendMessage("Username available"); + this.session.sendMessage("Username available 👌"); this.session.sendMessage(`Username _*${message.username}*_ is available, and I've reserved it for you :)`); this.session.sendPrompt("password", PASSWORD_PROMPT); this.setMessageHandler(this.receivePassword); diff --git a/server/seed.js b/server/seed.js index 9ac7fe5..248276e 100755 --- a/server/seed.js +++ b/server/seed.js @@ -30,7 +30,7 @@ export class Seeder { // const passwordHash = "1000:bdaa0d7436caeaa4d278e7591870b68c:151b8f7e73a97a01af190a51b45ee389c2f4590a6449ddae6f25b9eab49cac0d"; const player = new Player("user", passwordHash); - this.game.players.set("user", player); + this.game.createPlayer("user", passwordHash); // const char = new Character(player.username, "Sir Debug The Strong", true); } diff --git a/server/server.js b/server/server.js index acbd170..056a441 100755 --- a/server/server.js +++ b/server/server.js @@ -7,7 +7,7 @@ import * as msg from "./utils/messages.js"; import * as cfg from "./utils/config.js"; import { Session } from "./models/session.js"; import { Seeder } from "./seed.js"; -import { AuthState } from "./models/states/auth.js"; +import { AuthState } from "./models/states/AuthState.js"; class MudServer {