things+stuff

This commit is contained in:
Kim Ravn Hansen
2025-09-10 12:36:42 +02:00
parent 5d0cc61cf9
commit ba293d08b3
39 changed files with 3425 additions and 2713 deletions

555
server/seeders/characerSeeder.js Normal file → Executable file
View File

@@ -3,13 +3,560 @@
// | | | '_ \ / _` | '__/ _` |/ __| __/ _ \ '__|
// | |___| | | | (_| | | | (_| | (__| || __/ |
// \____|_| |_|\__,_|_| \__,_|\___|\__\___|_|
//
// ____ _
// / ___| ___ ___ __| | ___ _ __
// \___ \ / _ \/ _ \/ _` |/ _ \ '__|
// ___) | __/ __/ (_| | __/ |
// |____/ \___|\___|\__,_|\___|_|
//
export class CharacterSeeder {
}
// ------------------------------------------------
import { Character } from "../models/character.js";
import { Game } from "../models/game.js";
import { Player } from "../models/player.js";
import * as roll from "../utils/dice.js";
import { isIdSane } from "../utils/id.js";
export class CharacterSeeder {
/** @type {Game} */
constructor(game) {
/** @type {Game} */
this.game = game;
}
/**
* Create an item, using an item blueprint with the given name
*
* @param {string} itemBlueprintId id of the item blueprint
* @returns {Item|undefined}
*/
item(itemBlueprintId) {}
/**
* @param {Character} character
* @param {...string} itemBlueprintIds
*/
addItemsToCharacter(character, ...itemBlueprintIds) {
for (const id of itemBlueprintIds) {
const blueprint = this.game.getItemBlueprint(id);
if (!blueprint) {
throw new Error(`No blueprint found for id: ${id}`);
}
const item = blueprint.createItem();
character.addItem(item);
}
}
/**
* @param {Character} character
* @param {...string} skills
*/
addSkillsToCharacter(character, ...skills) {
for (const skill of skills) {
if (!isIdSane(skill)) {
throw new Error(`Skill id >>${skill}<< is insane!`);
}
character.skills.add(skill);
}
}
/**
* Foundation function
* @name FoundationFunction
* @function
* @param {Character} The character to which we apply this foundation.
*/
createCharacter() {
const c = new Character();
//
// Initializing
//
// Rolling skills
c.awareness = roll.d6() + 2;
c.grit = roll.d6() + 2;
c.knowledge = roll.d6() + 2;
c.magic = roll.d6() + 2;
c.meleeCombat = roll.d6() + 2;
c.rangedCombat = roll.d6() + 2;
c.skulduggery = roll.d6() + 2;
switch (roll.d8()) {
case 1:
c.ancestry = "human";
// Humans get +1 to all skills
c.awareness++;
c.grit++;
c.knowledge++;
c.magic++;
c.meleeCombat++;
c.rangedCombat++;
c.skulduggery++;
break;
case 2:
c.ancestry = "dwarven";
c.meleeCombat = Math.max(c.meleeCombat, 10);
break;
case 3:
c.ancestry = "elven";
c.rangedCombat = Math.max(c.rangedCombat, 10);
break;
case 4:
c.ancestry = "giant";
c.meleeCombat = Math.max(c.grit, 10);
break;
case 5:
c.ancestry = "Gnomish";
c.meleeCombat = Math.max(c.awareness, 10);
break;
case 6:
c.ancestry = "primordial";
c.meleeCombat = Math.max(c.magic, 10);
break;
case 7:
c.ancestry = "draconic";
c.meleeCombat = Math.max(c.knowledge, 10);
break;
case 8:
c.ancestry = "demonic";
c.meleeCombat = Math.max(c.skulduggery, 10);
break;
default:
throw new Error("Logic error, ancestry d8() roll was out of scope");
}
this.applyFoundation(c);
}
/**
* Create characters for the given player
*
* The characters are automatically added to the player's party
*
* @param {Player} player
* @param {number} partySize
*
* @return {Character[]}
*/
createParty(player, partySize) {
//
for (let i = 0; i < partySize; i++) {
const character = this.createCharacter(player);
}
}
/**
* @param {Character} c
* @param {string|number} Foundation to add to character
*/
applyFoundation(c, foundation = "random") {
switch (foundation) {
case "random":
return this.applyFoundation(c, roll.dice(3));
break;
//
// Brawler
// ------
case 1:
case ":brawler":
c.foundation = "Brawler";
c.skills.add(":armor.light");
c.silver = 40;
c.maxHitPoints = c.currentHitPoints = 15;
c.itemSlots = 7;
c.meleeCombat = Math.max(c.meleeCombat, 10);
c.knowledge = Math.min(c.knowledge, 10);
this.addItemsToCharacter(
c, //
":armor.light.studded_leather",
":weapon.weird.spiked_gauntlets",
);
this.addSkillsToCharacter(c, ":weapon.weird.spiked_gauntlets");
break;
//
// DRUID
// ------
case 2:
case ":druid":
c.foundation = "Druid";
c.silver = 40;
c.maxHitPoints = this.currentHitPoints = 15;
c.itemSlots = 7;
c.meleeCombat = Math.max(this.meleeCombat, 10);
c.knowledge = Math.min(this.knowledge, 10);
this.addItemsToCharacter(
c, //
":armor.light.leather",
":weapon.light.sickle",
":kits.poisoners_kit",
":kits.healers_kit",
);
this.addSkillsToCharacter(
c, //
":armor.light.leather",
":armor.light.hide",
":weapon.light.sickle",
);
break;
case 3:
case ":fencer":
c.foundation = "Fencer";
//
// Stats
c.maxHitPoints = c.currentHitPoints = 15;
c.meleeCombat = Math.max(c.meleeCombat, 10);
c.magic = Math.min(c.magic, 10);
//
// Skills
this.addSkillsToCharacter(
c, //
":perk.two_weapons", // TODO: perks should be their own thing, and not a part of the skill system?
":armor.light",
);
//
// Gear
c.silver = 40;
c.itemSlots = 5;
this.addItemsToCharacter(
c, //
":armor.light.leather",
":weapon.light.rapier",
":weapon.light.dagger",
);
/*
//
//---------------------------------------------------------------------------------------
//HEADLINE: Fencer
//---------------------------------------------------------------------------------------
| {counter:foundation}
|Fencer
|[unstyled]
* Light Armor
|[unstyled]
* Leather
* Rapier
* Dagger
* 40 Silver Pieces
|[unstyled]
//
//---------------------------------------------------------------------------------------
//HEADLINE: GUARD
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Guard
|[unstyled]
* Medium Armor
|[unstyled]
* Halberd
* Bull's Eye Lantern
* Signal Whistle
* Map of Local Area
* 50 Silver Pieces
|[unstyled]
* 10 Hit Points
* 5 Item Slots
* Awareness raised to 10
* Melee Combat raised to 10
* Skulduggery limited to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: MAGICIAN
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Magician
|[unstyled]
* None
|[unstyled]
* Tier 2 Wand with random spell.
* Tier 1 Wand with random spell.
* 10 Silver Pieces
|[unstyled]
* 10 Hit Points
* 6 Item Slots
* Melee Combat limited to 10
* Ranged Combat limited to 5
* Magic raised to 10
* Grit limited to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: MEDIC
//---------------------------------------------------------------------------------------
| {counter:foundation}
|Medic
|[unstyled]
* Light Armor
* Medium Armor
|[unstyled]
* Club
* Sling
* 3 Daggers
* Healer's Kit
* 40 Silver Pieces
|[unstyled]
* 10 Hit Points
* 6 Item Slots
//
//---------------------------------------------------------------------------------------
//HEADLINE: RECKLESS
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Reckless
|[unstyled]
|[unstyled]
* Great Axe
* 50 Silver Pieces
|[unstyled]
* 20 Hit Points
* 7 Item Slots
* Melee Combat raised to 10
* Awareness raised to 10
* Grit raised to 10
* Magic limited to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: ROVER
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Rover
|[unstyled]
* Light Armor
|[unstyled]
* Leather Armor
* Short Sword
* Longbow
* Snare Maker's Kit
* 25 Silver Pieces
|[unstyled]
* 10 Hit Points
* 5 Item Slots
* Magic Reduced to 10
* Awareness raised to 10
* Ranged Combat raised to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: SKIRMISHER
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Skirmisher
|[unstyled]
* Light Armor
* Shields
|[unstyled]
* Spear
* Small Shield
* 50 Silver Pieces
|[unstyled]
* 15 Hit Points
* 6 Item Slots
* Melee Combat raised to 10
* Awareness raised to 10
* Skulduggery raised to 10
* Grit raised to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: SNEAK
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Sneak
|[unstyled]
* Light Armor
|[unstyled]
* 3 daggers
* Small Crossbow
* Poisoner's Kit
* 30 Silver Pieces
|[unstyled]
* 10 Hit Points
* 6 Item Slots
* Melee Combat raised to 10
* Awareness raised to 10
* Skulduggery raised to 10
* Grit raised to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: SPELLSWORD
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Spellsword
|[unstyled]
|[unstyled]
* Tier 1 Wand with random spell.
* Longsword
* 30 Silver Pieces
|[unstyled]
* 12 Hit Points
* 5 Item Slots
* Melee Combat raised to 10
* Ranged Combat limited to 10
* Magic raised to 10
* Skulduggery limited to 10
* Grit raised to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: SPELUNKER
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Spelunker
|[unstyled]
* None
|[unstyled]
* Spear
* Caltrops
* Bull's Eye Lantern
* Map Maker's Kit
* Chalk
* Caltrops
* 5 Silver Pieces
|[unstyled]
* 10 Hit Points
* 4 Item Slots
* Awareness raised to 10
* Melee Combat raised to 10
* Skulduggery raised to 10
* Magic limited to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: SPIT'N'POLISH
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Spit'n' Polish
|[unstyled]
* Heavy Armor
* Shield
|[unstyled]
* Half-Plate
* Large Shield
* Long Sword
* 10 Silver Pieces
|[unstyled]
* 10 Hit Points
* 2 Item Slots
* Melee Combat raised to 10
* Magic Reduced to 6
* Awareness Reduced to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: STILETTO
//---------------------------------------------------------------------------------------
| {counter:foundation}
| Stiletto
|[unstyled]
* Light Armor
|[unstyled]
* Padded Armor
* 3 Daggers
* Small Crossbow
* Poisoner's Kit
* 20 Silver Pieces
|[unstyled]
* 10 Hit Points
* 5 Item Slots
* Melee Combat raised to 10
* Ranged Combat raised to 10
* Awareness raised to 10
* Magic limited to 6
* Knowledge limited to 10
//
//---------------------------------------------------------------------------------------
//HEADLINE: Tinkerer
//---------------------------------------------------------------------------------------
| {counter:foundation}
|Tinkerer
|[unstyled]
* Light Armor
|[unstyled]
* Studded Leather
* Wrench (club)
* Tinkerer's Kit
* 30 Silver Pieces
|[unstyled]
* 10 Hit Points
* 5 Item Slots
* Awareness raised to 10
* Knowledge raised to 10
*/
//
// WTF ?!
// ------
default:
throw new Error(`Invalid foundation id ${foundation}`);
}
}
}

12
server/seeders/gameSeeder.js Normal file → Executable file
View File

@@ -1,4 +1,5 @@
import { Game } from "../models/game.js";
import { CharacterSeeder } from "./characerSeeder.js";
import { ItemSeeder } from "./itemSeeder.js";
import { PlayerSeeder } from "./playerSeeder.js";
@@ -10,11 +11,9 @@ import { PlayerSeeder } from "./playerSeeder.js";
* If dev mode, we create some known debug logins. (username = user, password = pass) as well as a few others
*/
export class GameSeeder {
/** @returns {Game} */
createGame() {
/** @type {Game} */
/** @protected @constant @readonly @type {Game} */
this.game = new Game();
this.work(); // Seeding may take a bit, so let's defer it so we can return early.
@@ -23,11 +22,12 @@ export class GameSeeder {
}
work() {
console.info("seeding...");
console.info("seeding");
//
(new PlayerSeeder(this.game)).seed(); // Create debug players
(new ItemSeeder(this.game)).seed(); // Create items, etc.
new PlayerSeeder(this.game).seed(); // Create debug players
new ItemSeeder(this.game).seed(); // Create items, etc.
new CharacterSeeder(this.game).createParty(this.game.getPlayer("user"), 3); // Create debug characters.
//
// Done

View File

@@ -1,5 +1,5 @@
import { Game } from "../models/game.js";
import { ItemTemplate } from "../models/item.js";
import { ItemBlueprint } from "../models/item.js";
//
// ___ _ _____ _ _
@@ -9,16 +9,15 @@ import { ItemTemplate } from "../models/item.js";
// |___|\__\___|_| |_| |_| |_|\___|_| |_| |_| .__/|_|\__,_|\__\___||___/
// |_|
//
// Seed the Game.itemTemplate store
// Seed the Game.ItemBlueprint store
export class ItemSeeder {
/** @param {Game} game */
constructor(game) {
this.game = game;
}
seed() {
//
// __ __
// \ \ / /__ __ _ _ __ ___ _ __ ___
// \ \ /\ / / _ \/ _` | '_ \ / _ \| '_ \/ __|
@@ -26,46 +25,88 @@ export class ItemSeeder {
// \_/\_/ \___|\__,_| .__/ \___/|_| |_|___/
// |_|
//-------------------------------------------------------
this.game.createItemTemplate("weapons.light.dagger", {
this.game.addItemBlueprint(":weapon.light.dagger", {
name: "Dagger",
description: "Small shady blady",
itemSlots: 0.5,
damage: 3,
melee: true,
skills: [":weapon.light"],
ranged: true,
specialEffect: "effects.weapons.fast",
specialEffect: ":effect.weapon.fast",
});
this.game.createItemTemplate("weapons.light.sickle", {
this.game.addItemBlueprint(":weapon.light.sickle", {
name: "Sickle",
description: "For cutting nuts, and branches",
itemSlots: 1,
damage: 4,
specialEffect: "effects.weapons.sickle",
skills: [":weapon.light"],
specialEffect: ":effect.weapon.sickle",
});
this.game.createItemTemplate("weapons.light.spiked_gauntlets", {
this.game.addItemBlueprint(":weapon.weird.spiked_gauntlets", {
name: "Spiked Gauntlets",
description: "Spikes with gauntlets on them!",
itemSlots: 1,
damage: 5,
skills: [
// Spiked gauntlets are :Weird so you must be specially trained to use them.
// This is done by having a skill that exactly matches the weapon's blueprintId
":weapon.weird.spiked_gauntlets",
],
specialEffect: "TBD",
});
//
// _
// / \ _ __ _ __ ___ ___ _ __ ___
// / _ \ | '__| '_ ` _ \ / _ \| '__/ __|
// / ___ \| | | | | | | | (_) | | \__ \
// /_/ \_\_| |_| |_| |_|\___/|_| |___/
// ---------------------------------------
//
this.game.createItemTemplate("armors.light.studded_leather", {
name: "Studded Leather",
this.game.addItemBlueprint(":armor.light.studded_leather", {
name: "Studded Leather Armor",
description: "Padded and hardened leather with metal stud reinforcement",
itemSlots: 3,
specialEffect: "TBD",
skills: [":armor.light"],
armorHitPoints: 10,
});
this.game.addItemBlueprint(":armor.light.leather", {
name: "Leather Armor",
description: "Padded and hardened leather",
itemSlots: 2,
specialEffect: "TBD",
skills: [":armor.light"],
armorHitPoints: 6,
});
console.log(this.game._itemTemplates);
console.log(this.game._itemBlueprints);
//
// _ ___ _
// | |/ (_) |_ ___
// | ' /| | __/ __|
// | . \| | |_\__ \
// |_|\_\_|\__|___/
// -------------------
this.game.addItemBlueprint(":kit.poisoners_kit", {
name: "Poisoner's Kit",
description: "Allows you to create poisons that can be applied to weapons",
itemSlots: 2,
specialEffect: "TBD",
count: 20,
maxCount: 20,
});
this.game.addItemBlueprint(":kit.healers_kit", {
name: "Healer's Kit",
description: "Allows you to heal your teammates outside of combat",
itemSlots: 2,
specialEffect: "TBD",
count: 20,
maxCount: 20,
});
}
}

View File

@@ -2,34 +2,33 @@ import { Game } from "../models/game.js";
import { Player } from "../models/player.js";
export class PlayerSeeder {
/** @param {Game} game */
constructor(game) {
/** @param {Game} game */
constructor(game) {
/** @type {Game} */
this.game = game;
}
/** @type {Game} */
this.game = game;
}
seed() {
// Examples of the word "pass" hashed by the client and then the server:
// Note that the word "pass" has gajillions of hashed representations, all depending on the salts used to hash them.
// "pass" hashed by client: KimsKrappyKryptoV1:userSalt:1000:SHA-256:b106e097f92ff7c288ac5048efb15f1a39a15e5d64261bbbe3f7eacee24b0ef4
// "pass" hashed by server: 1000:15d79316f95ff6c89276308e4b9eb64d:2178d5ded9174c667fe0624690180012f13264a52900fe7067a13f235f4528ef
//
// Since the server-side hashes have random salts, the hashes themselves can change for the same password.
// The client side hash must not have a random salt, otherwise, it must change every time.
//
// The hash below is just one has that represents the password "pass" sent via V1 of the "Kims Krappy Krypto" scheme.
seed() {
// Examples of the word "pass" hashed by the client and then the server:
// Note that the word "pass" has gajillions of hashed representations, all depending on the salts used to hash them.
// "pass" hashed by client: KimsKrappyKryptoV1:userSalt:1000:SHA-256:b106e097f92ff7c288ac5048efb15f1a39a15e5d64261bbbe3f7eacee24b0ef4
// "pass" hashed by server: 1000:15d79316f95ff6c89276308e4b9eb64d:2178d5ded9174c667fe0624690180012f13264a52900fe7067a13f235f4528ef
//
// Since the server-side hashes have random salts, the hashes themselves can change for the same password.
// The client side hash must not have a random salt, otherwise, it must change every time.
//
// The hash below is just one has that represents the password "pass" sent via V1 of the "Kims Krappy Krypto" scheme.
this.game.createPlayer(
"user",
"1000:15d79316f95ff6c89276308e4b9eb64d:2178d5ded9174c667fe0624690180012f13264a52900fe7067a13f235f4528ef",
"userSalt",
);
this.game.createPlayer(
"user",
"1000:15d79316f95ff6c89276308e4b9eb64d:2178d5ded9174c667fe0624690180012f13264a52900fe7067a13f235f4528ef",
"userSalt",
);
this.game.createPlayer(
"admin",
"1000:a84760824d28a9b420ee5f175a04d1e3:a6694e5c9fd41d8ee59f0a6e34c822ee2ce337c187e2d5bb5ba8612d6145aa8e",
"adminSalt",
);
}
this.game.createPlayer(
"admin",
"1000:a84760824d28a9b420ee5f175a04d1e3:a6694e5c9fd41d8ee59f0a6e34c822ee2ce337c187e2d5bb5ba8612d6145aa8e",
"adminSalt",
);
}
}