// ____ _ _ // / ___| |__ __ _ _ __ __ _ ___| |_ ___ _ __ // | | | '_ \ / _` | '__/ _` |/ __| __/ _ \ '__| // | |___| | | | (_| | | | (_| | (__| || __/ | // \____|_| |_|\__,_|_| \__,_|\___|\__\___|_| // ____ _ // / ___| ___ ___ __| | ___ _ __ // \___ \ / _ \/ _ \/ _` |/ _ \ '__| // ___) | __/ __/ (_| | __/ | // |____/ \___|\___|\__,_|\___|_| // ------------------------------------------------ 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.magic = Math.min(c.magic, 10) c.meleeCombat = Math.max(c.meleeCombat, 10) this.addProficienciesToCharacter( c, // ":perk.riposte", ":armor.light", ":weapon.light", ); this.addItemsToCharacter( c, // ":armor.light.leather", ":weapon.basic.dagger", ":weapon.light.rapier", ); } if (foundation === 4 || foundation === ":guard") { c.foundation = "Guard"; c.silver = 50; c.itemSlots = 5; c.maxHitPoints = c.currentHitPoints = 10 c.awareness = Math.max(c.awareness, 10) c.meleeCombat = Math.max(c.meleeCombat, 10) c.skulduggery = Math.min(c.skulduggery, 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.lamp_oil", ":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.grit = Math.min(c.grit, 10) c.magic = Math.max(c.magic, 10) c.meleeCombat = Math.min(c.meleeCombat, 10) c.rangedCombat = Math.min(c.rangedCombat, 10) /* ---- 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 c.awareness = Math.max(10, c.awareness) this.addProficienciesToCharacter( c, // ":armor.light", ":armor.medium", ":weapon.light", ); this.addItemsToCharacter( c, // ":armor.light.studded_leather", ":kit.healers_kit", ":weapon.basic.club", ":weapon.basic.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.awareness = Math.max(10, c.awareness); c.grit = Math.max(10, c.grit); c.magic = Math.min(10, c.magic); c.meleeCombat = Math.max(10, c.meleeCombat); this.addProficienciesToCharacter( c, // ":wepaon.heavy", ":wepaon.light", ); 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.awareness = Math.max(10, c.awareness) c.magic = Math.min(10, c.magic) c.rangedCombat = Math.max(10, c.rangedCombat) this.addProficienciesToCharacter( c, // ":armor.light", ":weapon.light", ":weapon.heavy.longbow", ); 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 } if (foundation === 9 || foundation === ":skrimisher") { c.foundation = "Skirmisher"; c.silver = 15; c.itemSlots = 6; c.maxHitPoints = c.currentHitPoints = 15 c.awareness = Math.max(10, c.awareness) c.grit = Math.max(10, c.grit) c.magic = Math.max(5, c.magic) c.meleeCombat = Math.max(10, c.meleeCombat) c.skulduggery = Math.max(10, c.skulduggery) this.addProficienciesToCharacter( c, // ":armor.light", ); this.addItemsToCharacter( c, // ":armor.light.small_shield", ":weapon.light.spear", ); return } if (foundation === 10 || foundation === ":sneak") { c.foundation = "Sneak"; c.silver = 30; c.itemSlots = 6; c.maxHitPoints = c.currentHitPoints = 10 c.awareness = Math.max(10, c.awareness) c.meleeCombat = Math.max(10, c.meleeCombat) c.skulduggery = Math.max(10, c.skulduggery) this.addProficienciesToCharacter( c, // ":armor.light", ); this.addItemsToCharacter( c, // ":weapon.basic.dagger", ":weapon.light.small_crossbow", ":kit.poisoners_kit", ); return } if (foundation === 11 || foundation === ":spellsword") { c.foundation = "Spellsword"; c.silver = 30; c.itemSlots = 5; c.maxHitPoints = c.currentHitPoints = 12; c.grit = Math.max(10, c.grit) c.magic = Math.max(10, c.magic) c.meleeCombat = Math.max(10, c.meleeCombat) c.rangedCombat = Math.min(10, c.rangedCombat) c.skulduggery = Math.min(10, c.skulduggery) this.addProficienciesToCharacter( c, // ":weapon.light", ); this.addItemsToCharacter( c, // ":weapon.light.rapier", // "[TODO TIER 1 WAND WITH RANDOM SPELL]", ); return } if (foundation === 12 || foundation === ":spelunker") { c.foundation = "Spelunker"; c.silver = 5; c.itemSlots = 4; c.maxHitPoints = c.currentHitPoints = 10; c.awareness = Math.max(10, c.awareness) c.magic = Math.min(10, c.magic) c.meleeCombat = Math.max(10, c.meleeCombat) c.skulduggery = Math.max(10, c.skulduggery) this.addProficienciesToCharacter( c, // ":weapon.light", ); this.addItemsToCharacter( c, // ":kit.map_makers_kit", ":lighting.bulls_eye_lantern", ":misc.caltrops", ":misc.chalk", ":misc.lamp_oil", ":weapon.light.spear", ); return } if (foundation === 13 || foundation === ":spit_n_polish") { c.foundation = "Spit'n'Polish"; c.silver = 10; c.itemSlots = 2; c.maxHitPoints = c.currentHitPoints = 10; c.magic = Math.min(6, c.magic) c.meleeCombat = Math.max(10, c.meleeCombat) this.addProficienciesToCharacter( c, // ":weapon.light", ":weapon.heavy", ":armor.heavy", ":armor.light", ); this.addItemsToCharacter( c, // ":weapon.heavy.longsword", ":armor.heavy.half_plate", ":armor.heavy.large_shield", ); return } if (foundation === 14 || foundation === ":stiletto") { c.foundation = "Stiletto"; c.silver = 10; c.itemSlots = 5; c.maxHitPoints = c.currentHitPoints = 10; c.magic = Math.min(6, c.magic) c.meleeCombat = Math.max(10, c.meleeCombat) this.addProficienciesToCharacter( c, // ":weapon.light", ":weapon.heavy", ":armor.heavy", ":armor.light", ); this.addItemsToCharacter( c, // ":weapon.heavy.longsword", ":armor.heavy.half_plate", ":armor.heavy.large_shield", ); return } return this.applyFoundation(c, ":random") } }