stuff
This commit is contained in:
@@ -173,8 +173,14 @@ export class Character {
|
|||||||
case 2:
|
case 2:
|
||||||
this.foundation = "druid";
|
this.foundation = "druid";
|
||||||
this.proficiencies.add("armor/natural");
|
this.proficiencies.add("armor/natural");
|
||||||
this.equipment.set("sickle", 1).set("poisoner's kit", 1).set("healer's kit", 1);
|
this.equipment
|
||||||
default: // case 2:
|
.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.foundation = "debug";
|
||||||
this.proficiencies.add("heavy_armor");
|
this.proficiencies.add("heavy_armor");
|
||||||
this.proficiencies.add("heavy_weapons");
|
this.proficiencies.add("heavy_weapons");
|
||||||
|
|||||||
@@ -30,12 +30,41 @@ export class Game {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* All players ever registered, mapped by name => player.
|
* 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
|
* @protected
|
||||||
* @type {Map<string,Player>} Map of users in the game username->Player
|
* @type {Map<string,Player>} Map of users in the game username->Player
|
||||||
*/
|
*/
|
||||||
_players = new Map();
|
_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,6 +69,13 @@ export class ItemTemplate {
|
|||||||
createItem() {
|
createItem() {
|
||||||
return new ChracterItem(this._id, this._name, this._description, this._itemSlots);
|
return new ChracterItem(this._id, this._name, this._description, this._itemSlots);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static getOrCreate(id, name, description, itemSlots) {
|
||||||
|
}
|
||||||
|
|
||||||
|
static seed() {
|
||||||
|
this
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -25,6 +25,21 @@ export class Player {
|
|||||||
return this._passwordHash;
|
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<Character>} */
|
/** @protected @type {Set<Character>} */
|
||||||
_characters = new Set();
|
_characters = new Set();
|
||||||
get characters() {
|
get characters() {
|
||||||
@@ -39,7 +54,7 @@ export class Player {
|
|||||||
this._username = username;
|
this._username = username;
|
||||||
this._passwordHash = passwordHash;
|
this._passwordHash = passwordHash;
|
||||||
|
|
||||||
this.createdAt = new Date();
|
this._createdAt = new Date();
|
||||||
}
|
}
|
||||||
|
|
||||||
setPasswordHash(hashedPassword) {
|
setPasswordHash(hashedPassword) {
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ export class Session {
|
|||||||
return this._game;
|
return this._game;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @type Date */
|
|
||||||
latestPing;
|
|
||||||
|
|
||||||
/** @type {Player} */
|
/** @type {Player} */
|
||||||
player;
|
player;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { Session } from "../session.js";
|
|
||||||
import * as msg from "../../utils/messages.js";
|
import * as msg from "../../utils/messages.js";
|
||||||
import * as security from "../../utils/security.js";
|
import * as security from "../../utils/security.js";
|
||||||
import { JustLoggedInState } from "./justLoggedIn.js";
|
|
||||||
import { CreatePlayerState } from "./createPlayer.js";
|
import { CreatePlayerState } from "./createPlayer.js";
|
||||||
|
import { JustLoggedInState } from "./justLoggedIn.js";
|
||||||
|
import { Session } from "../session.js";
|
||||||
|
|
||||||
const STATE_EXPECT_USERNAME = "promptUsername";
|
const STATE_EXPECT_USERNAME = "promptUsername";
|
||||||
const STATE_EXPECT_PASSWORD = "promptPassword";
|
const STATE_EXPECT_PASSWORD = "promptPassword";
|
||||||
@@ -43,7 +43,7 @@ export class AuthState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (this.subState === STATE_EXPECT_PASSWORD) {
|
if (this.subState === STATE_EXPECT_PASSWORD) {
|
||||||
this.receivePassword(message)
|
this.receivePassword(message);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,11 +81,7 @@ export class AuthState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.session.game.players.size === 0) {
|
const player = this.session.game.getPlayer(message.username);
|
||||||
console.error("there are no players registered");
|
|
||||||
}
|
|
||||||
|
|
||||||
const player = this.session.game.players.get(message.username);
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// handle invalid username
|
// handle invalid username
|
||||||
@@ -142,9 +138,13 @@ export class AuthState {
|
|||||||
if (!security.verifyPassword(message.password, this.session.player.passwordHash)) {
|
if (!security.verifyPassword(message.password, this.session.player.passwordHash)) {
|
||||||
this.session.sendError("Incorrect password!");
|
this.session.sendError("Incorrect password!");
|
||||||
this.session.sendPrompt("password", PASSWORD_PROMPT);
|
this.session.sendPrompt("password", PASSWORD_PROMPT);
|
||||||
|
this.session.player.failedPasswordsSinceLastLogin++;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.session.player.lastSucessfulLoginAt = new Date();
|
||||||
|
this.session.player.failedPasswordsSinceLastLogin = 0;
|
||||||
|
|
||||||
//
|
//
|
||||||
// Password correct, check if player is an admin
|
// Password correct, check if player is an admin
|
||||||
if (this.session.player.isAdmin) {
|
if (this.session.player.isAdmin) {
|
||||||
|
|||||||
0
server/models/states/awaitCommands.js
Normal file → Executable file
0
server/models/states/awaitCommands.js
Normal file → Executable file
@@ -1,7 +1,5 @@
|
|||||||
import figlet from "figlet";
|
import figlet from "figlet";
|
||||||
import { Session } from "../session.js";
|
import { Session } from "../session.js";
|
||||||
import WebSocket from "ws";
|
|
||||||
import { AuthState } from "./auth.js";
|
|
||||||
import { ClientMessage } from "../../utils/messages.js";
|
import { ClientMessage } from "../../utils/messages.js";
|
||||||
import { PARTY_MAX_SIZE } from "../../config.js";
|
import { PARTY_MAX_SIZE } from "../../config.js";
|
||||||
import { frameText } from "../../utils/tui.js";
|
import { frameText } from "../../utils/tui.js";
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import WebSocket from "ws";
|
|
||||||
import { Session } from "../session.js";
|
import { Session } from "../session.js";
|
||||||
import * as msg from "../../utils/messages.js";
|
import * as msg from "../../utils/messages.js";
|
||||||
import * as security from "../../utils/security.js";
|
import * as security from "../../utils/security.js";
|
||||||
@@ -79,11 +78,11 @@ export class CreatePlayerState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const taken = this.session.game.players.has(message.username);
|
const player = this.session.game.createPlayer(message.username);
|
||||||
|
|
||||||
//
|
//
|
||||||
// handle taken/occupied username
|
// handle taken/occupied username
|
||||||
if (taken) {
|
if (player === false) {
|
||||||
// Telling the user right away that the username is taken can
|
// Telling the user right away that the username is taken can
|
||||||
// lead to data leeching. But fukkit.
|
// lead to data leeching. But fukkit.
|
||||||
|
|
||||||
@@ -92,18 +91,9 @@ export class CreatePlayerState {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this._player = new Player(message.username, undefined);
|
this._player = player;
|
||||||
|
|
||||||
// _____ _
|
this.session.sendMessage("Username available 👌");
|
||||||
// | ___(_)_ ___ __ ___ ___
|
|
||||||
// | |_ | \ \/ / '_ ` _ \ / _ \
|
|
||||||
// | _| | |> <| | | | | | __/
|
|
||||||
// |_| |_/_/\_\_| |_| |_|\___|
|
|
||||||
//
|
|
||||||
// 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 _*${message.username}*_ is available, and I've reserved it for you :)`);
|
this.session.sendMessage(`Username _*${message.username}*_ is available, and I've reserved it for you :)`);
|
||||||
this.session.sendPrompt("password", PASSWORD_PROMPT);
|
this.session.sendPrompt("password", PASSWORD_PROMPT);
|
||||||
this.setMessageHandler(this.receivePassword);
|
this.setMessageHandler(this.receivePassword);
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ export class Seeder {
|
|||||||
//
|
//
|
||||||
const passwordHash = "1000:bdaa0d7436caeaa4d278e7591870b68c:151b8f7e73a97a01af190a51b45ee389c2f4590a6449ddae6f25b9eab49cac0d";
|
const passwordHash = "1000:bdaa0d7436caeaa4d278e7591870b68c:151b8f7e73a97a01af190a51b45ee389c2f4590a6449ddae6f25b9eab49cac0d";
|
||||||
const player = new Player("user", passwordHash);
|
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);
|
// const char = new Character(player.username, "Sir Debug The Strong", true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import * as msg from "./utils/messages.js";
|
|||||||
import * as cfg from "./utils/config.js";
|
import * as cfg from "./utils/config.js";
|
||||||
import { Session } from "./models/session.js";
|
import { Session } from "./models/session.js";
|
||||||
import { Seeder } from "./seed.js";
|
import { Seeder } from "./seed.js";
|
||||||
import { AuthState } from "./models/states/auth.js";
|
import { AuthState } from "./models/states/AuthState.js";
|
||||||
|
|
||||||
class MudServer {
|
class MudServer {
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user