Pre-release
Adventure.js Docs Downloads
Score: 0 Moves: 0
// Settings.js
(function () {
  /*global adventurejs A*/
  "use strict";

  /**
   *
   * @class adventurejs.Settings
   * @ajsinternal
   * @param {Game} game A reference to the game instance.
   * @ajsnavheading FrameworkReference
   * @summary Manages settings for a {@link adventurejs.Game|Game} instance.
   * @classdesc
   * <p>
   * <strong>Settings</strong> is a repository for global Game
   * options. Settings is created automatically
   * by {@link adventurejs.Game|Game}. This is an internal class
   * that authors should not need to construct. However,
   * authors can set options from their game file as
   * shown below, or change them during runtime
   * with calls to <a href="#set">MyGame.settings.set()</a>.
   * </p>
   * <h3 class="examples">Example:</h3>
   * <pre class="display"><code class="language-javascript">var MyGame = new adventurejs.Game( "MyGame", "GameDisplay" );
   * MyGame.settings.set({
   *   max_undos: 20,
   *   show_room_names_in_exit_descriptions: false,
   *   can_auto_open_apertures: false
   * });
   * </code></pre>
   */
  class Settings {
    constructor(game) {
      this.game = game;

      /**
       * Each level of undo stores a snapshot of the entire
       * game state in memory. Raising a game's undo count
       * will increase its memory requirements.
       * @var {Number} adventurejs.Settings#max_undos
       * @default 10
       */
      this.max_undos = 10;

      /**
       * Console logging: control the types of statements that print to console.
       * Useful for debugging.
       * 0 = critical ( warnings & errors only )
       * 1 = high
       * 2 = medium
       * 3 = low
       * @var {Number} adventurejs.Settings#log_level
       * @default 1
       */
      this.log_level = 1;

      /**
       * Trace log statements: control which log levels call console.trace()
       * Useful for debugging.
       * @var {Number} adventurejs.Settings#log_trace_level
       * @default 0
       */
      this.log_trace_level = 0;

      /**
       * Console logging: control what keywords print to console.
       * Warnings and Errors will print regardless of settings.
       * See the available list of keywords below. Comment any
       * to remove those from console output.
       * @var {Number} adventurejs.Settings#log_level
       * @default 1
       */
      this.log_keywords = {
        tutorial: true,
        exit: true,
        character: true,
        tangible: true,
        behavior: true /* onX functions */,
        copyoperations: true /* clone, merge, diff, restore, save */,
        dictionary: true,
        display: true,
        game: true,
        parser: true,
        print: true,
        room: true,
        saverestore: true,
        vessel: true,
        substanceemitter: true,
        substancemixer: true,
        travel: true,
        utility: true,
        verb: true,
        verbs: true,
      };

      /**
       * If set true, when player tries to go in a direction that hasn't
       * got an {@link adventurejs.Exit|Exit},
       * print the current room's Exits as a reminder
       * of what Exits are available. Though this is a global setting,
       * {@link adventurejs.Room|Rooms}
       * can have their own setting. Room setting overrides global setting.
       * @var {Boolean} adventurejs.Settings#when_travel_fails_list_exits
       * @default true
       */
      this.when_travel_fails_list_exits = true;

      /**
       * If set true,
       * {@link adventurejs.Exit|Exit} descriptions can include
       * the name of the {@link adventurejs.Room|Room} that the
       * Exit leads to. (The logic for this may also consider other
       * conditions such as whether the player knows about the
       * other Room.) Though this is a global setting,
       * Exits can have their own setting. Exit setting overrides
       * global setting.
       * @var {Boolean} adventurejs.Settings#show_room_names_in_exit_descriptions
       * @default true
       */
      this.show_room_names_in_exit_descriptions = true;

      /**
       * If set true,
       * {@link adventurejs.Exit|Exit} descriptions can include
       * the name of the {@link adventurejs.Room|Room} that the
       * Exit leads to once player knows about the destination Room.
       * Generally player must visit a room to know about it, but
       * there can be exceptions.
       * Though this is a global setting,
       * Exits can have their own setting. Exit setting overrides
       * global setting.
       * @var {Boolean} adventurejs.Settings#show_room_names_in_exit_descriptions_only_when_room_is_known
       * @default false
       */
      this.show_room_names_in_exit_descriptions_only_when_room_is_known = false;

      /**
       * If set true,
       * {@link adventurejs.Exit|Exit} descriptions can include
       * the name of the {@link adventurejs.Room|Room} that the
       * Exit leads to once player has used the Exit.
       * Though this is a global setting,
       * Exits can have their own setting. Exit setting overrides
       * global setting.
       * @var {Boolean} adventurejs.Settings#show_room_names_in_exit_descriptions_only_after_exit_has_been_used
       * @default true
       */
      this.show_room_names_in_exit_descriptions_only_after_exit_has_been_used = true;

      /**
       * If true, disambiguation prompts will present an ordered
       * list rather than plain text.
       * <br><br>
       * 'Disambiguation' means the player has asked for an object
       * that could refer to multiple things, and the
       * {@link adventurejs.Parser|Parser}
       * hasn't been able to narrow it down to one object.
       * Let's say there are three cats of different colors in the room.
       * Normally the game would ask the player:
       * <pre class="display">
       * which cat did you mean, the orange cat, the black cat, or the piebald cat?
       * </pre>
       * If this is true, prompts will look like this instead:
       * <pre class="display">
       * Which cat did you mean?
       * 1) the orange cat
       * 2) the black cat
       * 3) the piebald cat
       * </pre>
       * When this is done, the player can enter 1, 2, or 3 and the parser
       * will understand their choice.
       * <br><br>
       * For more information about Disambiguation, see
       * <a href="/doc/NextSteps_Disambiguation.html">How to Disambiguate</a>
       * @var {Boolean} adventurejs.Settings#show_disambiguation_as_ordered_list
       * @default false
       */
      this.show_disambiguation_as_ordered_list = false;

      /**
       * If true, disambiguation prompts will present a plain text
       * paragraph with numbered options in it.
       * <br><br>
       * 'Disambiguation' means the player has asked for an object
       * that could refer to multiple things, and the
       * {@link adventurejs.Parser|Parser}
       * hasn't been able to narrow it down to one object.
       * Let's say there are three kinds of pizza in the room.
       * Normally the game would ask the player:
       * <pre class="display">
       * Which pizza did you mean, the pepperoni pizza,
       * the pineapple pizza, or the vegetable pizza?
       * </pre>
       * If this is true, numbers will be inserted into the text,
       * like this:
       * <pre class="display">
       * Which pizza did you mean, 1) the pepperoni pizza,
       * 2) the pineapple pizza, or 3) the vegetable pizza?
       * </pre>
       * When this is done, the player can enter 1, 2, or 3 and the parser
       * will understand their choice.
       * <br><br>
       * For more information about Disambiguation, see
       * <a href="/doc/NextSteps_Disambiguation.html">How to Disambiguate</a>
       * @var {Boolean} adventurejs.Settings#
       * @default true
       */
      this.show_disambiguation_as_numbered_paragraph = true;

      /**
       * If true, when player enters a blank line,
       * treat it as if player typed "look".
       * Otherwise play dumb.
       * @var {Boolean} adventurejs.Settings#if_input_is_empty_print_room_description
       * @default false
       */
      this.if_input_is_empty_print_room_description = false;

      /**
       * if_input_is_empty_print_this can return string or array or function.
       * @var {String|Array|Function} adventurejs.Dictionary#if_input_is_empty_print_this
       * @default "I didn't see any input."
       */
      this.if_input_is_empty_print_this = `I didn't see any input. `;

      /**
       * When the parser can't understand a player's input, it prints
       * a generic statement using this string. The statement is
       * also used in some other arbitrary places, which is to say,
       * players may see it a lot.
       * If you would like to customize this output
       * you can do so by setting this property to your preferred
       * output and including $(input) as a token to be replaced
       * with the player's input. For example:
       * <pre class="display"><code class="language-javascript">MyGame.settings.set({
       *   if_parser_has_no_response_print_this: "I didn't understand <em class='unparsed'>$(input)</em>. ",
       * });
       * </code></pre>
       * This example wraps the player's input in an <em> element so that
       * it can be styled differently from the rest of the output. This
       * isn't required and you can omit the <em> if you prefer.
       * And in fact you don't even need to include the original input
       * if you prefer not to. Just omit <code>$(input)</code> from your
       * string and the string will be printed as you set it.
       * <br><br>
       * <code>if_parser_has_no_response_print_this</code> supports the use of
       * <code>{@link adventurejs.Game#getStringOrArrayOrFunction|getStringOrArrayOrFunction}</code>
       * meaning that you can vary it up if you like by setting it to
       * a rotating array of strings or a contextually aware function.
       * @var {Boolean} adventurejs.Settings#if_parser_has_no_response_print_this
       * @default true
       */
      this.if_parser_has_no_response_print_this = `I don't understand what you mean by <em class='unparsed'>$(input)</em>. `;

      /**
       * If true, when player enters just a noun,
       * treat it as if player typed "examine x".
       * Otherwise play dumb.
       * @var {Boolean} adventurejs.Settings#if_input_is_an_asset_name_examine_it
       * @default true
       */
      this.if_input_is_an_asset_name_examine_it = true;

      /**
       * We're all about strings here. Your job as an author is
       * to write lots and lots and lots of them. Sometimes it
       * gets boring seeing the same string over and over again,
       * and you just want to show some alternates.
       * Some properties let you do that through the use of
       * {@link adventurejs.Game#getStringOrArrayOrFunction|getStringOrArrayOrFunction}.
       * You can feed getStringOrArrayOrFunction a string
       * or an array or a function (that returns a string).
       * If you provide an array, this setting determines
       * whether strings in the array are presented in sequence,
       * or randomly. If this setting is true, arrays
       * will be randomized.
       * <br><br>
       * For more information, see
       * <a href="/doc/Scripting_StringArrayFunction.html">How to Use String|Array|Function</a>
       * @var {Boolean} adventurejs.Settings#randomize_arrays_in_getStringOrArrayOrFunction
       * @default true
       * @todo List properties that this applies to.
       */
      this.randomize_arrays_in_getStringOrArrayOrFunction = true;

      /**
       * When players apply a verb to a group of objects,
       * such as "take all",
       * if any of those objects have a verb hook,
       * that may create awkward output. For example, let's say
       * there are four medallions and one is cursed, using
       * verb_hooks.take,
       * and the player inputs "take all medallions":
       * <pre class="display">
       * "You take the gold medallion. You take the brass medallion.
       * Oh no, taking the onyx medallion transported you to the
       * nether realms! You take the silver medallion."
       * </pre>
       * This option tries to provide a little bit of control over that
       * by saying: if player picks up these four objects, take the cursed
       * one last. It doesn't solve all problems. For instance, if
       * two of our medallions each took the player to a different location,
       * that might break something. Some situations can't be solved
       * programmatically and just come down to good game design.
       * <br><br>
       * Alternately, you may want to handle assets with verb_hooks
       * first. Let's say you have an explosive item, and if player inputs
       * "take all" you want the explosive to be picked up first and then
       * end the turn in order to prevent other items from handled in the
       * same turn.
       * <br><br>
       * For more information on verb_hooks, see
       * <a href="/doc/Scripting_VerbPhases.html">Verb Phases</a>
       * <br><br>
       * Valid values are: -1 (move to beginning of queue), 0 (leave in place),
       * 1 (move to end of queue).
       * Warning: may cause undesired side effects.
       * @var {Boolean} adventurejs.Settings#move_verb_hooks_to_end_of_queue
       * @default true
       */
      // @TODO this block no longer works since moving verb_hooks into verb subscriptions
      this.enqueue_assets_with_verb_hooks = { order: 1 }; // valid values: -1, 0, 1

      /**
       * See the explanation for
       * <a href="#move_verb_hooks_to_end_of_queue">move_verb_hooks_to_end_of_queue</a>
       * above. This reverses that. When using a verb on multiple items,
       * you can choose to move items with custom functions to the beginning
       * of the queue. If this option is set true, it will override
       * move_verb_hooks_to_end_of_queue. Included for
       * completionism. Warning: this may cause undesired side effects.
       * @var {Boolean} adventurejs.Settings#move_verb_hooks_to_start_of_queue
       * @default false
       */
      // @TODO this block no longer works since moving verb_hooks into verb subscriptions
      this.move_verb_hooks_to_start_of_queue = false;

      /**
       * If true, {@link goTo} {@link adventurejs.Verb|Verb} will
       * {@link open} closed {@link adventurejs.Aperture|Apertures} that
       * lie between the start {@link adventurejs.Room|Room}
       * and the destination Room, but only if player has already
       * opened those Apertures before.
       * @var {Boolean} adventurejs.Settings#can_auto_open_apertures
       * @default true
       */
      this.can_auto_open_apertures = true;

      /**
       * If true, {@link goTo} {@link adventurejs.Verb|Verb} will
       * {@link unlock} locked {@link adventurejs.Aperture|Apertures} that
       * lie between the start {@link adventurejs.Room|Room}
       * and the destination Room, but only if player has already
       * unlocked those Apertures before
       * and is carrying any necessary {@link adventurejs.Key|Keys}.
       * @var {Boolean} adventurejs.Settings#can_auto_unlock_apertures
       * @default true
       */
      this.can_auto_unlock_apertures = true;

      /**
       * If true, {@link goTo} {@link adventurejs.Verb|Verb} will
       * {@link unseal} sealed {@link adventurejs.Aperture|Apertures} that
       * lie between the start {@link adventurejs.Room|Room}
       * and the destination Room, but only if player has already
       * unsealed those Apertures and is carrying any necessary {@link adventurejs.Key|Keys}.
       * @var {Boolean} adventurejs.Settings#can_auto_unseal_apertures
       * @default true
       */
      this.can_auto_unseal_apertures = true;

      /**
       * If true, when player {@link tie_noun1_to_noun2|ties} a
       * {@link adventurejs.Rope|Rope} to another asset,
       * {@link adventurejs.Game|Game} will move the asset into
       * player's inventory, using
       * {@link adventurejs.Tangible#onMoveThatToThis|player.onMoveThatToThis()}.
       * <br><br>
       * If the asset in that situation is coded with a verb event hook,
       * it has the potential to cause undesirable side effects.
       * It's an unlikely edge case, but if you find yourself in
       * that situation, try setting this to false to avoid side effects.
       * @var {Boolean} adventurejs.Settings#on_tie_rope_to_takeable_object_take_object
       * @default true
       */
      this.on_tie_rope_to_takeable_object_take_object = true;

      /**
       * If true, when player ties a rope to its maximum number
       * of tied points, will remove rope from player's inventory.
       * <br><br>
       * If the rope in that situation has a verb event hook for
       * {@link drop},
       * it has the potential to cause undesirable side effects.
       * It's an unlikely edge case, but if you find yourself in
       * that situation, try setting this to false to avoid side effects.
       * @var {Boolean} adventurejs.Settings#on_tie_rope_to_max_objects_drop_rope
       * @default true
       */
      this.on_tie_rope_to_max_objects_drop_rope = true;

      /**
       * If true, and player unties a rope they are not holding, which
       * is tied to an object that is not in the player's inventory,
       * if the rope is takeable, player will take it.
       * <br><br>
       * If the rope in that situation has a verb event hook for
       * {@link drop},
       * it has the potential to cause undesirable side effects.
       * It's an unlikely edge case, but if you find yourself in
       * that situation, try setting this to false to avoid side effects.
       * @var {Boolean} adventurejs.Settings#on_untie_rope_from_object_outside_inventory_take_rope
       * @default true
       */
      this.on_untie_rope_from_object_outside_inventory_take_rope = true;

      /**
       * Standard value for drinking, in ml. If player
       * {@link drink|drinks} from a
       * {@link adventurejs.Vessel|Vessel},
       * this is the quantity of liquid that will be removed from it
       * (unless the Vessel has custom drink logic).
       * @var {Number} adventurejs.Settings#mouthful
       * @default 100
       */
      this.mouthful = 100;

      /**
       * Standard value for holding in hands, in ml. Generally
       * if player tries to interact with substances without a container
       * they're given some kind of "slips through your fingers" message
       * but in the case of something like "throw sand" when player is
       * on a beach, we'll let them throw a handful of sand,
       * and if the containing object is not infinite, subtract this
       * amount from it.
       * @var {Number} adventurejs.Settings#handful
       * @default 100
       */
      this.handful = 100;

      /**
       * Room temperature in celsius.
       * @var {Number} adventurejs.Settings#room_temperature
       * @default 20
       * @todo celsius/farenheit conversion
       */
      this.room_temperature = 20;

      /**
       * States lookup for substances.
       * @var {Object} adventurejs.Settings#states
       */
      this.states = {
        SOLID: "SOLID",
        LIQUID: "LIQUID",
        GAS: "GAS",
      };

      /**
       * The <code>pronouns</code> setting is a lookup table
       * for use with <code>person</code>.
       * @var {Object} adventurejs.Settings#pronouns
       */
      this.pronouns = {
        FIRST: "first",
        SECOND: "second",
        PLURAL: "plural",
        MALE: "male",
        FEMALE: "female",
        NONBINARY: "nonbinary",
        NONHUMAN: "nonhuman",
      };

      /**
       * The <code>person</code> setting allows you to change
       * the pronouns in all of the default response strings
       * built-in to Adventurejs. For instance, if take
       * has a response that says <code>You pick up the axe.</code>
       * you can change the word You to I in this and all other
       * responses by setting person from seconds to first.
       * @var {String} adventurejs.Settings#person
       */
      this.person = this.pronouns.SECOND;

      // settings so far unused

      this.ambient_gravity = 1;
      this.ambient_light = 1;
      this.light_sources_are_cumulative = false;
      this.ambient_sound = 0;

      this.things_float_on_water = true;
      this.things_sink_in_water = true;
      this.things_disappear_in_water = true;

      this.things_rise_in_low_gravity = true;
      this.things_fall_in_high_gravity = true;
      this.things_disappear_in_space = true;

      this.verbosity = 0;
      this.print_verbose_room_descriptions_on_first_visit = true;
      this.min_verbosity = -2;
      this.max_verbosity = 1;
      // only -2 to 1 are supported
      // -2 = briefer, -1 = brief, 0 = description, 1 = verbose

      /**
       * If <code>print_debug_messages</code> is true, some failure responses may print
       * additional information to the game display.
       * Intended as a debugging aid.
       * @var {String} adventurejs.Settings#print_debug_messages
       */
      this.print_debug_messages = false;

      /**
       * If true, room/zone events will print. See
       * <a href="/doc/NextSteps_SceneEvents.html">Scene Events</a>
       * for more information.
       * @var {Boolean} adventurejs.Settings#enable_events
       * @default true
       */
      this.enable_events = true;

      /**
       * If player inputs something like
       * "throw rock at water", the word "water" may be
       * ambiguous because multiple water containers are
       * present: a glass of water, a bottle of water, a lake.
       * If any of those containers is a body of water like
       * a lake or ocean, we can probably assume that the
       * player meant "throw rock in lake". If
       * <code>throw_at_substance_prefers_bodies</code>
       * is true, the verb <code>throw</code>'s logic will
       * make that assumption, rather than prompting player
       * to specify an indirect object.
       * @var {Boolean} adventurejs.Settings#throw_at_substance_prefers_bodies
       * @default true
       */
      this.throw_at_substance_prefers_bodies = true;

      /**
       * If true, some verbs, on receiving input of sentence
       * structure "verb noun" may infer an indirect object
       * if one is required and player is carrying it.
       * This pattern is intended
       * chiefly for locks and keys, with the idea being that
       * if player inputs "unlock door" while carrying a door
       * key, the game can infer the door key instead of
       * prompting the player for a key, which may seem
       * obnoxious in some circumstances.
       * It works with lock / unlock, seal / unseal, open / close,
       * and some others.
       * @var {Boolean} adventurejs.Settings#try_to_infer_indirect_objects
       * @default true
       */
      this.try_to_infer_indirect_objects = true;

      /**
       * If true, <code>try_to_infer_indirect_objects</code> will
       * only be applied once a player has already interacted
       * with the direct object. This pattern is intended to
       * prevent giving away puzzle solutions. For example,
       * perhaps there are many keys but only one that opens
       * a certain lock, and determining which key opens the
       * lock is part of a puzzle. If this is set to true,
       * it would prevent players from being able to unlock
       * the door without trying all the keys to find the
       * correct one.
       * @var {Boolean} adventurejs.Settings#infer_indirect_objects_only_after_interaction
       * @default true
       */
      this.infer_indirect_objects_only_after_interaction = true;

      /**
       * If true, when <code>try_to_infer_indirect_objects</code>
       * results in multiple assets, prompt player to select one.
       * @var {Boolean} adventurejs.Settings#if_inferred_multiple_indirect_objects_then_prompt
       * @default true
       */
      this.if_inferred_multiple_indirect_objects_then_prompt = false;

      /**
       * Set the visibility of the compass rose in the status bar.
       * @var {Boolean} adventurejs.Settings#show_compass_rose_in_status
       * @default true
       */
      this._show_compass_rose_in_status = true;

      /* *
       * Set the status bar compass rose to show a magnified
       * version on hover.
       * @var {Boolean} adventurejs.Settings#magnify_compass_rose_on_hover
       * @default true
       */
      // Object.defineProperty(this, 'magnify_compass_rose_on_hover', {
      //   configurable: true,
      //   get() { return this.__magnify_compass_rose_on_hover; },
      //   set(value) {
      //     this.__magnify_compass_rose_on_hover = value;
      //     if(this.game.display) this.game.display.exitsContainerEl.classList[value?'add':'remove']('magnify');
      //   },
      // });
      // this.magnify_compass_rose_on_hover = false;

      /* *
       * When we initialize assets, By default we add
       * the name, each word of the name and each pair
       * of words in the name. In some situations this
       * may be excessive and lead to the parser
       * misinterpreting phrases.
       * @var {Boolean} adventurejs.Settings#split_asset_names_for_world_lookup
       * @default true
       */
      //this.split_asset_names_for_world_lookup = true;

      /**
       * We can track the user's y position. This option sets the maximum distance that the
       * player can reach vertically, ie when climbing an object like a tree. 1 is considered
       * to be about the height of a person.
       * @var {Boolean} adventurejs.Settings#reach_height
       * @default 1
       */
      this.reach_height = 1;

      /**
       * We can track the user's x/z position. This option sets the maximum distance that the
       * player can reach horizontally within a room. 0.33 is considered to be about arm's length.
       * @var {Boolean} adventurejs.Settings#reach_length
       * @default .33
       */
      this.reach_length = 0.33;

      /**
       * We can track the user's y position. This option sets the default distance that the
       * player travels vertically, ie when climbing an object like a tree. 1 is considered
       * to be about the height of a person.
       * @var {Boolean} adventurejs.Settings#climb_height
       * @default 1
       */
      this.climb_height = 1;

      /**
       * We can track the user's x/z position. This option sets the default distance that the
       * player travels horizontally when moving within a room. 1 is considered to be about
       * the height of a person.
       * @var {Boolean} adventurejs.Settings#stride_length
       * @default .33
       */
      this.stride_length = 0.33;

      /**
       * We can track the user's x/z position. This option sets the maximum distance that the
       * player travels horizontally when moving within a room. 1 is considered to be about
       * the height of a person.
       * @var {Boolean} adventurejs.Settings#jump_length
       * @default 1
       */
      this.jump_length = 1;

      /**
       * We can track the user's x/z position. This option sets whether
       * x/z are considered in determining reachability. By default,
       * all tangible assets are positioned at x:0,y:0,z:0, and reachability
       * checks don't consider x/z (though y is always considered). Now imagine
       * you have a dorm room with a bed and a desk, where the bed is at
       * x:3 and the desk is across the room at x:-3. If this property is true,
       * then their relative distance apart will be taken into account
       * when determining reachability.
       * @var {Boolean} adventurejs.Settings#xz_determines_reachability
       * @default false
       */
      this.xz_determines_reachability = true;
    }

    /**
     * Provides a chainable shortcut method for setting a number of properties on the instance.
     * @method adventurejs.Settings#set
     * @param {Object} props A generic object containing properties to copy to the instance.
     * @returns {adventurejs.Settings} Returns the instance the method is called on (useful for chaining calls.)
     * @chainable
     */
    set(props) {
      return A.deepSet.call(this.game, props, this);
    }

    get show_compass_rose_in_status() {
      return this._show_compass_rose_in_status;
    }
    set show_compass_rose_in_status(value) {
      this._show_compass_rose_in_status = value;
      if (this.game.display)
        this.game.display.exitsContainerEl.classList[value ? "remove" : "add"](
          "hidden"
        );
    }
  }
  adventurejs.Settings = Settings;
})();