create_bag.js

import { createItem } from "./create_item.js";
import { logger } from "./utils.js";
import { roll, test } from "./random_utils.js";
import Bag from "./models/bag.js";

/**
 * Creates a collection of objects, held in a collection type known as a Bag.
 *
 * @param [params] {Object}
 *   @param [params.value=10] {Number | String} the total value of items to add to the bag.
 *     The method will get as close to this value as possible, but may go over. If the
 *     value is a string, it is treated as dice notation.
 *   @param [params.repeat=20] {Number} This is the percentage chance that the last item
 *     selected will be added again. Can range from 0% (no repeated items) to 100% (only
 *     one item, as many as are needed to reach the other specified limits).
 *   @param [params.count=Number.MAX_VALUE] {Number} affects how many items will be added to
 *     the bag. The bag will not exceed this number of items.
 *   @param [params.enc=Number.MAX_VALUE] {Number} the maximum encumbrance of the bag.
 *     The method will get as close to this value as possible, but may go over.
 *   @param [params.uncountable=false] {Boolean} if true, items in the bag are treated as
 *     uncountable.
 *   @param [params.minValue=0] {Number} the minimum value of any items added to the bag (for
 *     some applications, like store stock, it is desirable to set this number above 0).
 *   @param [params.tags="*"] {String} tags that will be used to search for items to add to the bag.
 *   @param [params.name] {String} a name for the bag.
 *   @param [params.description] {String} a description for the bag.
 *   @param [params.condition] {String} if the item selected has the “br” tag and thus has
 *     condition, you can fix it’s condition by passing it in to this method.
 *
 * @example
 * createBag({ count: 3, tags: "house"}).toString()
 * => "A television (5/20), a winter jacket (worn; 0.5/10), and a ceramic bowl (3/1)."
 *
 * @returns {Bag} a bag containing items that meet the specified criteria
 */
export function createBag({
  value = 10,
  repeat = 5,
  enc = Number.MAX_VALUE,
  count = Number.MAX_VALUE,
  uncountable = false,
  minValue = 0,
  tags = "*",
  name,
  description,
  condition,
} = {}) {
  if (typeof value === "string") {
    value = roll(value);
  }
  logger.start("createBag", {
    tags,
    value,
    repeat,
    enc,
    count,
    uncountable,
    name,
    description,
    condition,
  });

  const bag = new Bag({ uncountable, name, description });
  const visitedItems = new Set();

  let currentValue = value;
  let currentEnc = enc;
  let currentCount = count;
  let item = null;

  outer: while (currentValue > 0 && currentEnc > 0 && currentCount > 0) {
    if (!test(repeat) || item === null) {
      let safetyLoops = count === Number.MAX_VALUE ? 200 : count * 50;
      do {
        item = createItem({
          tags,
          minValue,
          maxEnc: currentEnc,
          maxValue: currentValue,
          condition,
        });
        if (item == null) {
          break outer; // criteria no longer match
        }
      } while (visitedItems.has(item.name + item.title) && safetyLoops-- > 0);
    }
    if (item !== null) {
      visitedItems.add(item.name + item.title);
      bag.add(item);
      currentCount--;
      // if item.value == 0 (or enc), decrement by a random value or you'll get as many
      // items as there are safety loops (unless count or enc is set).
      currentValue -= item.value || roll(5);
      currentEnc -= item.enc || roll(5);
    }
  }
  return logger.end(bag);
}