db/item_database.js

import { random } from "../random_utils.js";
import { tagFilter } from "./database.js";
import { toTag } from "../string_utils.js";
import Item from "../models/item.js";
import RarityDatabase from "./rarity_database.js";

function parser(line) {
  let parts = line.split("!");
  let names = parts[0].trim().split(/\s*;\s*/);
  let [all, value, freq, enc] = parts[1].match(/(.*)([ruc])(.*)/);
  let tagArray = parts[2]
    .trim()
    .split(/\s+/)
    .filter((s) => s !== "");
  let frequency = freq === "c" ? "common" : freq === "u" ? "uncommon" : "rare";
  let tags = new Set(tagArray);
  var consumes;
  if (tags.has("cons")) {
    consumes = tagArray[tagArray.indexOf("cons") + 1];
  }
  let results = [];
  let params = {
    value: parseFloat(value),
    consumes,
    enc,
    frequency,
    tags,
  };
  names.forEach((name) => {
    params.name = name;
    add(results, params);
  });
  return results;
}

function add(array, params) {
  let item = new Item(params);
  item.tags.add(toTag(params.name)); // add name in tag form
  array.push(item);
}

/**
 * Specialized database for storing and querying game items with rarity-based selection.
 * Extends RarityDatabase to provide item-specific filtering and search capabilities.
 *
 * This class uses a specific parser when configuration data is added via the `add` method.
 * The format is as follows:
 *
 * `name1; name2!valueRarityEnc!tag1 tag2 tag3 ...`
 *
 * One or more names in the first position should be separated with semicolons. The
 * valueRarityEnc value should be in the format `#r#`, e.g. `50c5` for a value of 50, common
 * rarity, and encumbrance of 5. Finally there can be one or more tags in the final
 * position, that must follow the `item.schema.data`.
 *
 * @class ItemDatabase
 * @extends RarityDatabase
 */
export default class ItemDatabase extends RarityDatabase {
  /**
   * Creates a new ItemDatabase instance.
   * @constructor
   */
  constructor() {
    super({ parser });
  }

  /**
   * Finds all items matching the specified criteria.
   * @param {Object} [options={}] - Search criteria
   * @param {string} [options.name] - Name substring to match
   * @param {string} [options.tags] - Tag filter string
   * @param {number} [options.minValue=0] - Minimum item value
   * @param {number} [options.maxValue=Number.MAX_VALUE] - Maximum item value
   * @param {number} [options.minEnc=0] - Minimum encumbrance
   * @param {number} [options.maxEnc=Number.MAX_VALUE] - Maximum encumbrance
   * @returns {Array<Item>} Array of matching Item objects
   *
   * @example
   * itemDb.findAll({ tags: "weapon sword", minValue: 10, maxValue: 100 });
   */
  findAll({
    name,
    tags,
    minValue = 0,
    maxValue = Number.MAX_VALUE,
    minEnc = 0,
    maxEnc = Number.MAX_VALUE,
  } = {}) {
    return this.table.rows
      .filter(
        (model) =>
          model.name.indexOf(name) > -1 ||
          (model.value >= minValue &&
            model.value <= maxValue &&
            model.enc >= minEnc &&
            model.enc <= maxEnc &&
            tagFilter(model, tags))
      )
      .map((item) => new Item(item));
  }

  /**
   * Finds a single random item matching the specified criteria,
   * weighted by rarity (rare items are less likely to be selected).
   * @param {Object} [options={}] - Search criteria
   * @param {string} [options.name] - Name substring to match
   * @param {string} [options.tags] - Tag filter string
   * @param {number} [options.minValue=0] - Minimum item value
   * @param {number} [options.maxValue=Number.MAX_VALUE] - Maximum item value
   * @param {number} [options.minEnc=0] - Minimum encumbrance
   * @param {number} [options.maxEnc=Number.MAX_VALUE] - Maximum encumbrance
   * @returns {Item|null} A random matching Item object, or null if none found
   *
   * @example
   * itemDb.findOne({ tags: "weapon", minValue: 5 });
   */
  findOne({
    name,
    tags,
    minValue = 0,
    maxValue = Number.MAX_VALUE,
    minEnc = 0,
    maxEnc = Number.MAX_VALUE,
  } = {}) {
    var freqs = this.table.weightedSort();
    while (freqs.length) {
      var freq = freqs.shift();
      var rows = this.table[freq].filter(
        (model) =>
          model.name.indexOf(name) > -1 ||
          (model.value >= minValue &&
            model.value <= maxValue &&
            model.enc >= minEnc &&
            model.enc <= maxEnc &&
            tagFilter(model, tags))
      );
      if (rows.length) {
        return new Item(random(rows));
      }
    }
    return null;
  }
}