create_kit.js

import { createGender, getProfessions } from "./create_character.js";
import { createItem } from "./create_item.js";
import { database as professionDb } from "./professions.js";
import { logger } from "./utils.js";
import { roll, random, shuffle, test } from "./random_utils.js";
import { TRAITS as T } from "./constants.js";
import Bag from "./models/bag.js";

// TODOs:
// remove radiation suit and chaps from "clothes" and make them profession-specific
// update the profession tags so you can do profession-specific weapons, clothing,
// tools.

function addAmmo(bag, firearm) {
  var ammoType = firearm.typeOf("ammo");
  var bundledAmmo = createItem({ tags: `ammo ${ammoType} bundled` });
  if (test(20) && bundledAmmo) {
    bag.add(bundledAmmo, roll("2d3"));
  } else {
    var ammo = createItem({ tags: `ammo ${ammoType} -bundled` });
    switch (ammo.frequency) {
      case "common":
        bag.add(ammo, roll("8d8"));
        break;
      case "uncommon":
        bag.add(ammo, roll("4d8"));
        break;
      case "rare":
        bag.add(ammo, roll("3d6"));
        break;
    }
  }
  if (firearm.is("pistol") && test("60")) {
    var holster = createItem({ tags: `holster` });
    bag.add(holster);
  }
}

function addUntil(...args) {
  let bag = args.shift();
  shuffle(args);
  for (let i = 0; i < args.length; i++) {
    let item = createItem({ tags: args[i] });
    if (item) {
      bag.add(item);
      return;
    }
  }
  console.warn("No item matches any of these tag sets: " + args.join("; "));
}

/**
 * Generate the possessions that would be on the person of an active NPC (e.g.
 * out on patrol, out for a night on the town, out on a raid or in the middle
 * of criminal activity).
 *
 * @param params {Object}
 *   @param [params.profession] {String|Profession} profession name or instance
 *   @param [params.gender] {String} gender of character (male or female)
 *   @param [params.traveling] {boolean} should this bag include extended equipment
 *    for exploring, and not just walk-abouts?
 *
 * @return {Object} An object of two bags, one under the property "clothing" and another
 *    under the bag of "possessions".
 */
export function createKit({
  profession = random(getProfessions()),
  gender = createGender(),
  traveling = false,
} = {}) {
  logger.start("createKit", { profession, gender, traveling });

  var clothing = new Bag();
  var possessions = new Bag();
  var selGender = gender.toLowerCase();

  var profConfig = professionDb.findOne({ name: profession });
  var profKit = profConfig.kit;

  // clothes. stuff isn't tagged by profession
  if (test(20)) {
    addUntil(
      clothing,
      `clothing body -armor (${profKit})`,
      `clothing body -armor (${selGender} | unisex)`
    );
  } else {
    addUntil(
      clothing,
      `clothing torso -armor -coat (${profKit})`,
      `clothing torso -armor -coat (${selGender} | unisex)`
    );
    addUntil(
      clothing,
      `clothing legs -armor (${profKit})`,
      `clothing legs -armor (${selGender} | unisex)`
    );
  }
  // shoes
  addUntil(clothing, `clothing feet (${profKit})`, `clothing feet (${selGender} | unisex)`);

  // hat
  if (test(60)) {
    addUntil(clothing, `clothing head (${profKit})`, `clothing head (${selGender} | unisex)`);
  } else if (test(20)) {
    addUntil(possessions, `armor head`);
  }
  if (test(20)) {
    addUntil(possessions, `armor body`, `armor torso`);
  }

  // weapons and ammo
  var firearm = createItem({ tags: `firearm` });
  var melee = createItem({ tags: `melee` });
  var result = roll("1d10");
  if (result < 4) {
    possessions.add(firearm);
    addAmmo(possessions, firearm);
  } else if (result < 8) {
    possessions.add(melee);
    possessions.add(firearm);
    addAmmo(possessions, firearm);
  } else {
    possessions.add(melee);
  }

  if (traveling) {
    var carry = createItem({ tags: `useful travel container` });
    if (carry) {
      possessions.add(carry);
    }

    // exploration: wayfinding
    // exploration: communication
    // personal effects
    for (var i = 0; i < roll("1d4-2"); i++) {
      var item = createItem({ tags: `(kit:* tiny) | accessory` });
      possessions.add(item);
    }

    // exploration: food
    for (var i = 0; i < roll("2d4"); i++) {
      var food = createItem({ tags: `food -candy (preserved | ration | fruit)` });
      possessions.add(food);
    }

    // Not entirely happy with the horse as a possession. The horse can carry stuff and probably
    // would be carrying some stuff like feed, there’s no way to describe this, nor the relationship
    // to the character. What if they have two horses, or six?
    createTransport({ bag: possessions, profConfig });
  }
  return logger.end({ clothing, possessions });
}

function hasTrait(prof, trait) {
  return prof.seeds.includes(trait) || prof.traits.includes(trait);
}

function createTransport({ bag, profConfig } = {}) {
  let chanceHorse = hasTrait(profConfig, T.HORSEBACK_RIDING) ? 60 : 20;
  let chanceMotorcycle = hasTrait(profConfig, T.MOTORCYCLING) ? 20 : 5;
  console.debug(`chanceHorse = ${chanceHorse}, chanceMotorcycle = ${chanceMotorcycle}`);

  // TODO: If they have a motorcycle, they have some amount of gas and gas in tanks.
  // If they have a horse, the horse probably has saddlebags and may be carrying feed
  let hasHorse = test(chanceHorse);
  let hasMotorcycle = test(chanceHorse);

  if (chanceHorse > chanceMotorcycle && hasHorse) {
    bag.add(createItem({ tags: "horse" }), 1);
  } else if (hasMotorcycle) {
    bag.add(createItem({ tags: "motorcycle" }), 1);
  }
}