create_location.js

import { bagSpecParser } from "./string_utils.js";
import { createContainer, newCreateBag } from "./create_bag.js";
import { createLocationNameInSequence } from "./create_location_name.js";
import { Location, Store } from "./models/location.js";
import { locationsLoader } from "./data_loaders.js";
import { logger } from "./utils.js";
import { random, selectElements } from "./random_utils.js";
import LocationDatabase from "./db/location_database.js";

const locations = await locationsLoader("locations.data");

export var database = new LocationDatabase();
database.add.apply(database, locations);
database.verify();

const LOCATION_TYPES = database.models
  .filter((m) => !m.room)
  .map((m) => m.type)
  .sort();

export function getLocationTypes() {
  return LOCATION_TYPES;
}

/**
 * Create a location. You must supply the type of a location template and an instance tree
 * will be created from that point in the template hierarchy, working downward throw all
 * child nodes templates, returning the resulting instance tree. Tags are not currently used
 * in selection of templates, but may be in the future.
 */
export function createLocation({ type = random(LOCATION_TYPES), tags } = {}) {
  logger.start("createLocation", { type, tags });
  let template = database.findOne({ type, tags });
  if (template) {
    let root = createInstance(new Location({}), 1, template);
    walkTemplate(root, root.seqValue, template);
    return logger.end(root);
  }
  return logger.end(null);
}

function walkTemplate(parent, parentSeqValue, template) {
  let elements = selectElements(template.ch);
  elements.forEach((type) => {
    let childTemplate = database.findOne({ type });
    if (!childTemplate) {
      console.error("Could not find template for " + type);
      return;
    }
    if (childTemplate.abstract) {
      // Abstract templates are not instantiated, but their children are processed
      walkTemplate(parent, parentSeqValue, childTemplate);
    } else {
      let childLocation = createInstance(parent, parentSeqValue, childTemplate);
      walkTemplate(childLocation, childLocation.seqValue, childTemplate);
    }
  });
}

function createInstance(parent, parentSeqValue, childTemplate) {
  let child = childTemplate.owner ? new Store(childTemplate) : new Location(childTemplate);
  // contents is what exists in a room or place, including containers that contain things
  child.contents = determineContents(childTemplate, "contents");
  // inventory is specifically for stores
  child.inventory = determineContents(childTemplate, "inventory");
  if (parent) {
    parent.addChild(child);
  }

  // childIndex is not the right value since the child could be in an unusual position. For example,
  // the main floor of a Radio Station could have the rooms Lobby, Break Room, Office and Office.
  // In this case the offices would be labeled #103 and #104, though you'd expect them to be
  // #101 and #102 since they are the first and second OFFICE on the floor. We get the offset,
  // which is based on index of the first occurrence of the type in the parent’s children.
  let offset = getOffset(child);

  let [childSeqValue, name] = createLocationNameInSequence({
    parent,
    child,
    childIndex: offset,
    parentSeqValue,
  });
  child.seqValue = childSeqValue;
  child.name = name;
  return child;
}

function getOffset(child) {
  let parent = child.parent;
  let childTypes = ((parent && parent.children) || []).map((ch) => ch.type);
  let first = childTypes.indexOf(child.type);
  let last = childTypes.lastIndexOf(child.type);
  return last - first;
}

function determineContents(template, propName) {
  return selectElements(template[propName])
    .map((child) => {
      if (child.startsWith("Bag")) {
        return newCreateBag(bagSpecParser(child));
      } else if (child.startsWith("Stockpile")) {
        return newCreateBag(bagSpecParser(child));
        // return createStockpile(bagSpecParser(child)); output of this sucks
      }
      return createContainer({ type: child });
    })
    .filter((el) => el !== null);
}