Pre-release
AdventureJS Docs Downloads
Score: 0 Moves: 0
// getModifiedDescription.js

(function () {
  /* global adventurejs A */
  var p = adventurejs.Game.prototype;
  /**
   * Get modified descriptions,
   * such as "look at this through magnifying glass with candle",
   * where "through magnifying glass, with candle" is a key at
   * asset.descriptions.look["through magnifying glass,
   * with candle"].
   * "look" is always the default description, but any identifier
   * can have modifiers.
   * <br><br>
   * This is scoped to Game rather than Asset because there are
   * cases that handle simple unclassed objects with description
   * properties, notably: properties of room_scenery.
   * @memberOf adventurejs.Game
   * @method adventurejs.Game#getModifiedDescription
   * @param {Object} options
   * @param {Object} options.asset The asset under consideration.
   * @param {String} options.identifier
   * @param {Boolean} options.find_modifiers Optional instruction to find view modifiers such as light sources and lenses.
   * @param {Boolean} options.fallback_base Optional instruction to fall back to asset's default description.
   * @param {Boolean} options.append_base Optional instruction to append modified description to default description.
   * @param {Boolean} options.prepend_base Optional instruction to prepend modified description to default description.
   * @return {String}
   */
  p.getModifiedDescription = function Game_getModifiedDescription({
    asset,
    identifier = "look",
    find_modifiers,
    fallback_base,
    append_base,
    prepend_base,
  }) {
    let msg = "";
    if (identifier === "at") identifier = "look";

    if (asset.proxy) {
      let proxy = this.game.getAsset(asset.proxy);
      if (proxy) {
        let proxy_description = this.game.getModifiedDescription({
          asset: proxy,
          identifier: identifier,
          find_modifiers: find_modifiers,
          fallback_base: fallback_base,
        });
        if (proxy_description) return proxy_description;
      }
    }

    var input = this.game.getInput();
    var base_description = this.getDescription({ asset, identifier }) || "";

    // does object have descriptions at all?
    if (!asset.descriptions) {
      return asset.description
        ? asset.description
        : fallback_base
          ? base_description
          : "";
    }

    // does this description identifier exist?
    if ("undefined" === typeof asset.descriptions[identifier]) {
      // if identifier was look then we got nuthin
      if (identifier === "look" || !asset.descriptions.look) {
        return fallback_base ? base_description : "";
      }

      // we have look - does it exist under look?
      if ("undefined" === typeof asset.descriptions.look[identifier]) {
        // no such identifier
        return fallback_base ? base_description : "";
      }

      // it exists - treat it as a modifier on look
      input.pushViewModifier(identifier, null, "input");
      identifier = "look";
    }

    // view_modifiers is expected to look like:
    // view_modifiers: [ { asset: ClassedObject, prep: "string", type: "string" } ]

    if (find_modifiers) {
      this.game.findViewModifiers();
    }

    if (input.view_modifiers.length === 0) {
      return fallback_base ? base_description : "";
    }

    // look for valid descriptions
    for (let index = 0; index < input.view_modifiers.length; index++) {
      let indirect_identifier = input.view_modifiers[index].identifier;
      let indirect_asset = input.view_modifiers[index].asset;

      let indirect_aspects;

      if (!indirect_identifier || !indirect_asset) {
        continue;
      }

      // account for equivalencies: for example,
      // in the case of glasses, binoculars, etc player might say
      // "look at x with binoculars" while author has coded for
      // "look at x through binoculars" and we'll allow it
      // though it means we have to search for all acceptable equivalencies
      indirect_aspects = [indirect_identifier]; // convert to array

      // "look with" may be equivalent to "look through"
      if (
        indirect_identifier === "with" &&
        indirect_asset.hasQuirk("look_with_means_look_through")
      ) {
        if (!indirect_aspects.includes("through"))
          indirect_aspects.push("through");
      }
      if (
        indirect_identifier === "through" &&
        indirect_asset.hasQuirk("look_with_means_look_through")
      ) {
        if (!indirect_aspects.includes("with")) indirect_aspects.push("with");
      }

      // "look in" may be equivalent to "look on"
      if (
        indirect_identifier === "in" &&
        indirect_asset.hasQuirk("in_means_on")
      ) {
        if (!indirect_aspects.includes("on")) indirect_aspects.push("on");
      }

      // save equivalencies
      for (let i = 0; i < indirect_aspects.length; i++) {
        let indirect_identifier = indirect_aspects[i];
        let target = `${indirect_identifier} ${indirect_asset.name}`;
        if (!input.view_modifiers[index].equivalencies) {
          input.view_modifiers[index].equivalencies = [];
        }
        if (!input.view_modifiers[index].equivalencies.includes(target)) {
          input.view_modifiers[index].equivalencies.push(target);
        }
      }
    }

    // example of a view_modifier
    // {aspect: 'with', asset: Candle, type: 'auto', equivalencies: Array(2)}
    // equivalencies: (2) ['with occult candle', 'through occult candle']
    let provided_modifier_targets = [];
    for (let index = 0; index < input.view_modifiers.length; index++) {
      provided_modifier_targets.push(input.view_modifiers[index].equivalencies);
    }

    // we're allowing authors to set view modifiers in any order:
    // for instance "in pit, from tree" or "from tree, in pit"
    // so we have to look for every possible combination
    let expanded_modifier_targets = A.generateCombinations(
      provided_modifier_targets
    );

    if ("string" === typeof asset.descriptions[identifier]) {
      return asset.descriptions[identifier];
    }

    let comparisons = [];
    let description_keys = Object.keys(asset.descriptions[identifier]);
    // loop through expanded_modifier_targets
    // inside each loop, loop through description keys
    for (let i = 0; i < expanded_modifier_targets.length; i++) {
      // console.warn(`expanded_modifier_targets ${i}`, expanded_modifier_targets);
      for (let j = 0; j < description_keys.length; j++) {
        // modifier can be greater than description
        // but description can not be greater than modifier
        // alternately
        // modifier can have elements that are not in description
        // description can't have any elements that are not in modifier
        if (
          description_keys[j].split(",").length >
          expanded_modifier_targets[i].split(",").length
        ) {
          continue;
        }
        let count = A.countCommonElements(
          expanded_modifier_targets[i],
          description_keys[j]
        );
        comparisons.push({
          view_modifiers: expanded_modifier_targets[i],
          description: description_keys[j],
          count: count,
        });
        // console.warn(`description_keys[${j}]:`, description_keys[j]);
      }
    }

    // if it finds a description that matches multiple conditions,
    // it prints that but if it finds multiple single conditions
    // it prints those
    let maxstack =
      this.game.settings.max_level_of_modified_descriptions_to_stack;
    let stacked_descriptions = [];
    let highestcount = -1;
    let highestindex = -1;

    // loop through to get the highest number of matching descriptions
    for (let i = 0; i < comparisons.length; i++) {
      // console.warn(`comparisons[${i}]`, comparisons[i]);
      let count = comparisons[i].count;
      let description_length = comparisons[i].description.split(",").length;
      if (count === description_length && count > highestcount) {
        highestcount = comparisons[i].count;
        highestindex = i;
      }
    }
    // console.warn(
    //   "getModifiedDescription > Highest count is " + highestcount,
    //   comparisons[highestindex]
    // );

    // now loop through again to get all items that match highest count
    for (let i = 0; i < comparisons.length; i++) {
      // console.warn(`comparisons[${i}]`, comparisons[i]);
      let count = comparisons[i].count;
      let description_length = comparisons[i].description.split(",").length;

      // if highest count is 1 and there are multiple descriptions, stack them
      if (
        /* count === 1 && */
        count <= maxstack &&
        count === description_length &&
        count === highestcount
      ) {
        stacked_descriptions.push(comparisons[i]);
      }

      // if highest count is more than 1, only print the first
      // this is to avoid overlaps
      // if (count > 1 && stacked_descriptions.length === 0) {
      if (count > maxstack && stacked_descriptions.length === 0) {
        stacked_descriptions.push(comparisons[i]);
      }
    }
    // console.warn(
    //   "getModifiedDescription > stacked_descriptions",
    //   stacked_descriptions
    // );

    if (highestcount > -1) {
      // save a record of use back to the original view_modifiers

      // switching from one to many
      // console.warn(
      //   `getModifiedDescription > comparisons[${highestindex}].description`,
      //   comparisons[highestindex].description
      // );
      // let modifiers_set = comparisons[highestindex].description;
      // let best_description =
      //   asset.descriptions[identifier][comparisons[highestindex].description];

      let modifiers_sets = "";
      let best_descriptions = [];
      for (let i = 0; i < stacked_descriptions.length; i++) {
        best_descriptions.push(
          asset.descriptions[identifier][stacked_descriptions[i].description]
        );
        if (modifiers_sets.length) modifiers_sets += ",";
        modifiers_sets += stacked_descriptions[i].description;
      }

      // switching from one to many
      // let modifiers = modifiers_set.split(",").map((item) => item.trim());
      let modifiers = modifiers_sets.split(",").map((item) => item.trim());

      let user_modified = false;
      for (let i = 0; i < modifiers.length; i++) {
        let modifier = modifiers[i];
        // console.warn({ modifier });
        if (!modifier.length) continue;
        for (let j = 0; j < input.view_modifiers.length; j++) {
          if (input.view_modifiers[j].string === modifier) {
            input.view_modifiers[j].used = true;
            if (input.view_modifiers[j].type === "input") user_modified = true;
          }
        }
      }

      // call the description(s)

      // this version prints one
      // msg = A.getSAF.call(this.game, best_description, asset);

      // this version prints many
      for (let i = 0; i < best_descriptions.length; i++) {
        // print the count number for debugging
        // msg += i + ") " + A.getSAF.call(this.game, best_descriptions[i], asset);

        msg += A.getSAF.call(this.game, best_descriptions[i], asset);
      }

      // did we want to prepend the base description?
      if (prepend_base || this.game.settings.concatenate_descriptions) {
        msg = base_description + msg;
      }

      // did we want to append the base description?
      if (append_base) {
        msg = msg + base_description;
      }

      return msg;
    }

    // fallback
    return fallback_base ? base_description : "";
  };
})();