// ____ _ _ // / ___| |__ __ _ _ __ __ _ ___| |_ ___ _ __ // | | | '_ \ / _` | '__/ _` |/ __| __/ _ \ '__| // | |___| | | | (_| | | | (_| | (__| || __/ | // \____|_| |_|\__,_|_| \__,_|\___|\__\___|_| // ____ _ // / ___| ___ ___ __| | ___ _ __ // \___ \ / _ \/ _ \/ _` |/ _ \ '__| // ___) | __/ __/ (_| | __/ | // |____/ \___|\___|\__,_|\___|_| // ------------------------------------------------ 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") } }