Files
muuhd/seeders/characerSeeder.js
Kim Ravn Hansen de96d45ade progress
2025-11-04 15:34:49 +01:00

607 lines
16 KiB
JavaScript
Executable File

// ____ _ _
// / ___| |__ __ _ _ __ __ _ ___| |_ ___ _ __
// | | | '_ \ / _` | '__/ _` |/ __| __/ _ \ '__|
// | |___| | | | (_| | | | (_| | (__| || __/ |
// \____|_| |_|\__,_|_| \__,_|\___|\__\___|_|
// ____ _
// / ___| ___ ___ __| | ___ _ __
// \___ \ / _ \/ _ \/ _` |/ _ \ '__|
// ___) | __/ __/ (_| | __/ |
// |____/ \___|\___|\__,_|\___|_|
// ------------------------------------------------
import { Character } from "../models/character.js";
import { gGame } from "../models/globals.js";
import { Player } from "../models/player.js";
import { isIdSane } from "../utils/id.js";
// stupid convenience hack that only works if we only have a single Game in the system.
// Which we easily could have.!!
let roll = {};
export class CharacterSeeder {
constructor() {
// stupid hack that ensures we populate roll AFTER gGame is available
// Which we easily could have.!!
roll.d = (max, min = 1) => gGame.random.within(min, max)
roll.d6 = () => roll.d(6)
roll.d8 = () => roll.d(8)
}
/**
* @param {Character} character
* @param {...string} itemBlueprintIds
*/
addItemsToCharacter(character, ...itemBlueprintIds) {
for (const id of itemBlueprintIds) {
const blueprint = gGame.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} proficiencies
*/
addProficienciesToCharacter(character, ...proficiencies) {
for (const prof of proficiencies) {
if (!isIdSane(prof)) {
throw new Error(`Proficiency id >>${prof}<< is insane!`);
}
character.proficiencies.add(prof);
}
}
/**
* Foundation function
* @name FoundationFunction
* @function
* @param {Character} The character to which we apply this foundation.
*/
createCharacter() {
const c = new Character();
this.generateName(c);
this.rollSkills(c);
this.applyAncestry(c);
this.applyFoundation(c);
return c;
}
generateName(c) {
/** @todo use actual random name generator */
c.name =
gGame.random.oneOf("sir ", "madam ", "mister ", "miss ", "", "", "") +
"random " + // name
gGame.random.next().toString();
}
rollSkills(c) {
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;
}
applyAncestry(c) {
let ancestryId = roll.d8();
switch (ancestryId) {
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 of ${ancestryId} was out of scope"`);
}
}
/**
* 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++) {
player.addCharacter(
this.createCharacter(player), //
);
}
}
/**
* @param {Character} c
* @param {string|number} Foundation to add to character
*/
applyFoundation(c, foundation = ":random") {
if (foundation == ":random") {
return this.applyFoundation(c, roll.d(20)); // according to the rulebook, roll a d20 and reroll any invalid results.
}
//
// Brawler
// ------
if (foundation === 1 || foundation === ":brawler") {
c.foundation = "Brawler";
c.maxHitPoints = c.currentHitPoints = 15;
c.itemSlots = 7;
c.silver = 40;
c.meleeCombat = Math.max(c.meleeCombat, 10);
c.knowledge = Math.min(c.knowledge, 10);
this.addProficienciesToCharacter(
c,
":armor.light",
":weapon.weird.spiked_gauntlets"
);
this.addItemsToCharacter(
c, //
":armor.light.studded_leather",
":weapon.weird.spiked_gauntlets",
);
return;
}
//
// DRUID
// ------
if (foundation === 2 || foundation === ":druid") {
c.foundation = "Druid";
c.silver = 10;
c.itemSlots = 5;
c.maxHitPoints = this.currentHitPoints = 10;
this.addProficienciesToCharacter(
c, //
":armor.light.cloth",
":armor.light.hide",
":armor.light.leather",
":kit.healers_kit",
":kit.poisoners_kit",
":weapon.light.sickle",
":weapon.light.quarterstaff",
":weapon.light.sling",
);
this.addItemsToCharacter(
c, //
":armor.light.leather",
":weapon.light.sickle",
":kit.poisoners_kit",
":kit.healers_kit",
);
return;
}
//
// FENCER
// -------
if (foundation === 3 || foundation === ":fencer") {
c.foundation = "Fencer";
c.silver = 40;
c.itemSlots = 5;
c.maxHitPoints = c.currentHitPoints = 15;
c.clamp("magic", 1, 10)
c.clamp("meleeCombat", 10, undefined)
this.addProficienciesToCharacter(
c, //
":perk.riposte",
":armor.light",
":weapon.light",
);
this.addItemsToCharacter(
c, //
":armor.light.leather",
":weapon.light.dagger",
":weapon.light.rapier",
);
}
if (foundation === 4 || foundation === ":guard") {
c.foundation = "Guard";
c.silver = 50;
c.itemSlots = 5;
c.maxHitPoints = c.currentHitPoints = 10
c.clamp("awareness", 10, undefined)
c.clamp("meleeCombat", 10, undefined)
c.clamp("skulduggery", 1, 10)
this.addProficienciesToCharacter(
c, //
":armor.medium",
":weapon.heavy",
":weapon.specialist.halberd",
":wepaon.light",
);
this.addItemsToCharacter(
c, //
":armor.medium.breastplate",
":lighting.bulls_eye_lantern",
":map.city.hovedstad",
":misc.signal_whistle",
":weapon.specialist.halberd",
);
return
}
if (foundation === 5 || foundation === ":magician") {
c.foundation = "Magician"
c.silver = 10;
c.itemSlots = 6
c.maxHitPoints = c.currentHitPoints = 10
c.clamp("grit", 0, 10)
c.clamp("magic", 10, undefined)
c.clamp("meleeCombat", 0, 10)
c.clamp("rangedCombat", 0, 5)
/* ---- NO PROFICIENCIES ---- */
this.addItemsToCharacter(
c, //
"TODO: [TEIR 2 WAND WITH RANDOM SPELL]",
"TODO: [TEIR 1 WAND WITH RANDOM SPELL]",
);
return
}
if (foundation === 6 || foundation === ":medic") {
c.foundation = "Medic";
c.silver = 40;
c.itemSlots = 7;
c.maxHitPoints = c.currentHitPoints = 10
this.addProficienciesToCharacter(
c, //
":armor.light",
":armor.medium",
":weapon.light",
);
this.addItemsToCharacter(
c, //
":armor.light.studded_leather",
":kit.healers_kit",
":weapon.light.club",
":weapon.light.dagger",
":weapon.light.dagger",
":weapon.light.dagger",
":weapon.light.sling",
);
return
}
if (foundation === 7 || foundation === ":reckless") {
c.foundation = "Reckless";
c.silver = 50;
c.itemSlots = 7;
c.maxHitPoints = c.currentHitPoints = 20
c.clamp("awareness", 10, undefined)
c.clamp("grit", 10, undefined)
c.clamp("magic", 1, 10)
c.clamp("meleeCombat", 10, undefined)
this.addProficienciesToCharacter(
c, //
":wepaon.heavy",
);
this.addItemsToCharacter(
c, //
":weapon.heavy.great_axe",
);
return
}
if (foundation === 8 || foundation === ":rover") {
c.foundation = "Rover";
c.silver = 25;
c.itemSlots = 5;
c.maxHitPoints = c.currentHitPoints = 10
c.clamp("awareness", 10, undefined)
c.clamp("magic", 1, 10)
c.clamp("rangedCombat", 10, undefined)
this.addProficienciesToCharacter(
c, //
":armor.light",
":weapon.light",
);
this.addItemsToCharacter(
c, //
":armor.light.leather",
":kit.snare_makers_kit",
":weapon.heavy.longbow",
":weapon.light.short_sword",
":map.shayland", // Shayland is the region around Hovedstad
);
return
}
/*
//
//---------------------------------------------------------------------------------------
//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
*/
return this.applyFoundation(c, ":random")
}
}