Pre-release
Adventure.js Docs Downloads
Score: 0 Moves: 0
// Tangible.js
(function () {
  /*global adventurejs A*/
  "use strict";
  /**
   * @ajspath adventurejs.Atom.Asset.Matter.Tangible
   * @augments adventurejs.Matter
   * @class adventurejs.Tangible
   * @ajsconstruct MyGame.createAsset({ "class":"Tangible", "name":"foo", [...] })
   * @ajsconstructedby adventurejs.Game#createAsset
   * @ajsnavheading BaseClasses
   * @param {String} game_name The name of the top level game object.
   * @param {String} name A name for the object, to be serialized and used as ID.
   * @summary Base class for all game objects with physical properties.
   * @tutorial Tangibles_AboutTangibles
   * @classdesc
   * <p>
   * <strong>Tangible</strong> is the base class for all
   * {@link adventurejs.Asset|Assets} with physical properties
   * in the game world, with the exception of Substances.
   * All of the properties of Tangible are inherited by all of
   * its subclasses, and most subclasses don't define new properties.
   * Most subclasses are essentially convenience methods that set
   * a group of properties particular to a type of object.
   * In theory, an instance of almost any subclass could be made to
   * behave like an instance of almost any other subclass,
   * simply by setting the right properties. This provides the
   * flexibility to mix and match properties to construct game
   * assets with customized behaviors.
   * </p>
   * <p>
   * For example, {@link adventurejs.Chair|Chair} defines
   * something you can sit on, and
   * {@link adventurejs.Bed|Bed} defines something
   * you can lie on, but perhaps you want to make something
   * like a divan, that you can sit on and lie on. You could
   * create a Chair and set asset.aspects.on.player.can.lie
   * property to true.
   * </p>
   * <pre class="display"><code class="language-javascript">MyGame.createAsset({
   *   class: "Chair",
   *   name: "divan",
   *   descriptions: {look:"A comfortable looking fainting couch. ",),
   *   aspects:
   *   {
   *     on: { player: { can: { lie: true } } }
   *   }
   * });
   * </code></pre>
   * This is a simple example of customizing an object, but hopefully
   * it gives you an idea of how flexible the Tangible class is.
   * Alternatively if you wanted to extensively customize your divan,
   * you could define your own Divan class to extend
   * {@link adventurejs.Furniture|Furniture}.
   **/
  class Tangible extends adventurejs.Matter {
    constructor(name, game_name) {
      super(name, game_name);
      this.class = "Tangible";

      this.is = new adventurejs.Tangible_Is("is", this.game_name, this.id).set({
        parent_id: this.id,
      });

      this.can = new adventurejs.Tangible_Can(
        "can",
        this.game_name,
        this.id
      ).set({
        parent_id: this.id,
      });

      this.must = new adventurejs.Tangible_Must(
        "must",
        this.game_name,
        this.id
      ).set({
        parent_id: this.id,
      });

      /**
       * Containing object for Aspects, aka "in", "out", "under", "behind"
       * etc.
       * @var {Object} adventurejs.Tangible#aspects
       * @default {}
       */
      this.aspects = {};

      // set direct verb subscriptions
      this.setDOVs([
        "attach",
        "detach",
        "go",
        "hit",
        "kick",
        "move",
        "poke",
        "pull",
        "push",
        "shake",
        //"throw",
      ]);

      // set indirect verb subscriptions
      this.setIOVs(["hit", "throw", "flick"]);

      //this.player_has_used = false;

      /**
       * It's possible for player to become nested to an asset without a
       * specified preposition. In such cases, this default aspect is used.
       * @var {String} adventurejs.Tangible#default_aspect
       * @default "on"
       */
      this.default_aspect = "on";

      /**
       * When player is nested, some verbs can only be applied to assets that
       * have been specified as being within reach.
       * @var {Array} adventurejs.Tangible#things_player_can_do_all_verbs_to_from_this
       * @default []
       * @todo Revisit this. Use things_within_reach for all reach settings?
       */
      this.things_player_can_do_all_verbs_to_from_this = [];

      /**
       * When player climbs asset, set their preposition to this default.
       * @var {String} adventurejs.Tangible#default_aspect_for_climb
       * @default "on"
       * @todo Move string to const
       */
      this.default_aspect_for_climb = "on";

      /**
       * Set whether 'climb' means 'go on', which might apply to assets
       * such as stairs and ladders.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!climb_means_go_on
       * @default false
       */
      this.quirks.climb_means_go_on = false;

      /**
       * Set whether 'climb' means 'stand on', which might apply to
       * furniture and things like boulders which players can stand on.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!climb_means_stand_on
       * @default false
       */
      this.quirks.climb_means_stand_on = false;

      /**
       * Set list of other assets that player can climb to from this.
       * @var {Array} adventurejs.Tangible#things_player_can_climb_to_from_this
       * @default []
       */
      this.things_player_can_climb_to_from_this = [];

      /**
       * Set whether 'crawl' means 'go on', which might apply to
       * furniture and things like boulders which players can stand on.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!crawl_means_go
       * @default false
       */
      this.quirks.crawl_means_go = true;

      /**
       * Set whether player can go off (aka get off) this asset. Used for Rooms.
       * @var {Boolean} adventurejs.Tangible#player_can_exit
       * @default false
       */
      this.player_can_exit = false;

      /**
       * Set whether 'stand' means 'go off' (aka 'get off'), which might apply
       * to furniture such as chairs.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!stand_means_get_off
       * @default false
       */
      this.quirks.stand_means_get_off = false;

      /**
       * Set whether 'get up' means 'go off' (aka 'get off'), which might apply
       * to furniture such as chairs or beds.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!get_up_means_get_off
       * @default false
       */
      this.quirks.get_up_means_get_off = false;

      /**
       * Set whether 'get on' means 'climb', which might apply
       * to things such as trees.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!go_on_means_climb
       * @default false
       */
      this.quirks.go_on_means_climb = false;

      /**
       * If set to true, "in" and "on" become interchangeable for some verbs.
       * Intended chiefly for things like chairs where "sit in chair"
       * and "sit on chair" can be interpreted to mean the same thing.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!in_means_on
       * @default false
       */
      this.quirks.in_means_on = false;

      /**
       * If set to true, "put" means "pour" for some verbs.
       * Intended chiefly for things like "put syrup on pancakes" which is
       * a common phrasing where clearly the speaker means to pour.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!put_means_pour
       * @default false
       */
      this.quirks.put_means_pour = false;

      /**
       * Set whether 'jump' means 'jump on' as in jumping up and down on a bed.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!jump_means_jump_on
       * @default true
       */
      this.quirks.jump_means_jump_on = true;

      /**
       * Set whether 'jump' means 'jump off' as in jumping off a tree.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!jump_means_jump_off
       * @default false
       */
      this.quirks.jump_means_jump_off = false;

      /**
       * List of other assets player can jump to while nested within this asset.
       * @var {Array} adventurejs.Tangible#things_player_can_jump_to_from_this
       * @default []
       */
      this.things_player_can_jump_to_from_this = [];

      /**
       * Set whether player can step on this asset.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!step_on_means_stamp_on
       * @default false
       */
      this.quirks.step_on_means_stamp_on = false;

      /**
       * Set whether ver 'step on' means 'stand on', as with a skateboard or some
       * types of furniture.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!step_on_means_stand_on
       * @default false
       */
      this.quirks.step_on_means_stand_on = false;

      /**
       * If player becomes nested within this asset via 'swing to', set the player's
       * posture to this default.
       * @var {Boolean} adventurejs.Tangible#default_posture_for_swing_to
       * @default false
       */
      this.default_posture_for_swing_to = "stand";

      /**
       * If player becomes nested within this asset via 'swing to', set the player's
       * preposition to this default.
       * @var {Boolean} adventurejs.Tangible#default_aspect_for_swing_to
       * @default false
       */
      this.default_aspect_for_swing_to = "on";

      /**
       * List of other assets that player can swing to while nested within this asset.
       * @var {Array} adventurejs.Tangible#things_player_can_swing_to_from_this
       * @default []
       */
      this.things_player_can_swing_to_from_this = [];

      /**
       * Set whether player can hang on this asset.
       * @var {Boolean} adventurejs.Tangible#player_can_hang_on_this
       * @default false
       */
      this.player_can_hang_on_this = false;

      /**
       * A string that describes the position the
       * {@link adventurejs.Asset|Asset}
       * is in, ex "resting" for most non-Character Assets,
       * or "lying", "sitting", "standing" etc for
       * {@link adventurejs.Characters|Characters}.
       * <code class="property">this.game.dictionary.getStringLookup( type, value )</code>.
       * Can be referenced in custom code through
       * <code class="property">MyGame.dictionary.getStringLookup( type, value )</code>.
       * @var {Boolean} adventurejs.Tangible#posture_position
       * @default "default"
       */
      this.posture_position = this.game.dictionary.getStringLookup(
        "posture_positions",
        "default"
      );

      /**
       * Set whether player knows a thing is hidden. Useful if player is the one doing
       * the hiding, or if player has found an object but not picked it up.
       * @var {Boolean} adventurejs.Tangible#player_knows_its_hidden
       * @default false
       */
      this.player_knows_its_hidden = false;

      /**
       * Set an asset's percent of buoyancy. No logic has been implemented around this.
       * @var {float} adventurejs.Tangible#buoyancy
       * @default 0
       * @todo Implement.
       */
      this.buoyancy = 0; // range 0 to 1

      /**
       * Set the amount of liquid an asset has absorbed.
       * No particular logic has been implemented around absorption.
       * @var {Boolean} adventurejs.Tangible#absorption_quantity
       * @default 0
       */
      this.absorption_quantity = 0;

      /**
       * Set whether a closed thing can be auto-opened. This is used chiefly for 'go to',
       * which tries to auto-open doors, something which authors might want to prevent
       * depending on other logic.
       * @var {Boolean} adventurejs.Tangible#can!auto_open
       * @default false
       */
      this.can.auto_open = false;

      /**
       * Set whether a locked thing can be auto-unlocked. This is used chiefly for 'go to',
       * which tries to auto-unlock doors, something which authors might want to prevent
       * depending on other logic.
       * @var {Boolean} adventurejs.Tangible#can!auto_unlock
       * @default false
       */
      this.can.auto_unlock = false;

      /**
       * Set whether a sealed thing can be auto-unsealed. This is used chiefly for 'go to',
       * which tries to auto-unseal apertures, something which authors might want to prevent
       * depending on other logic.
       * @var {Boolean} adventurejs.Tangible#can!auto_unseal
       * @default false
       */
      this.can.auto_unseal = false;

      /**
       * Set whether 'pick' means 'unlock'. This is provided to give authors the option
       * to choose whether pick and unlock are treated as distinct verbs.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!pick_means_unlock
       * @default false
       */
      this.quirks.pick_means_unlock = false;

      /**
       * Set a message to print for asset's single use.
       * @var {Boolean} adventurejs.Tangible#use_once_message
       * @default false
       */
      this.use_once_message = "";

      /* *
       * Tangible Assets may have numerous descriptions, though all but
       * default are optional.
       * @var {Object} adventurejs.Tangible#descriptions
       * @default { look: "", careful: "", long: "", short: "", brief: "", verbose: "", closed: "", open: "", taste: "", smell:"", listen:"", in:"", through:"", }
       */
      this.descriptions = {
        look: "",
        brief: "",
        verbose: "",
        careful: "",
        closed: "",
        open: "",
        taste: "",
        touch: "",
        smell: "",
        sound: "",
        exits: "",
        for_exits_list: "",
        behind: "",
        in: "",
        on: "",
        over: "",
        through: "",
        under: "",
      };

      /* *
       * @var {String} adventurejs.Tangible#description
       * @default ""
       */
      //this.description = "";//"$(We) see nothing worth mentioning.";

      /**
       * Set whether this asset should be destroyed when drunk.
       * @var {Boolean} adventurejs.Tangible#on_drink_destroy
       * @default false
       */
      this.on_drink_destroy = false;

      /**
       * Set whether this asset should be emptied when drunk,
       * versus only subtracting a mouthful as set in Settings.
       * @var {Boolean} adventurejs.Tangible#on_drink_empty
       * @default false
       */
      this.on_drink_empty = false;

      /**
       * Set whether this asset should be destroyed when eaten.
       * @var {Boolean} adventurejs.Tangible#on_eat_destroy
       * @default false
       */
      this.on_eat_destroy = false;

      /**
       * Set whether "look with" is equivalent to "look through". For example,
       * in the case of a telescope, "look with telescope" is equivalent to
       * "look through telescope". In the case of a window, you can look through
       * it, but not look with it. In the case of a candle, you can look with it,
       * but not look through it.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!look_with_means_look_through
       * @default false
       */
      this.quirks.look_with_means_look_through = false;

      /**
       * This property was superseded by the GraduatedController class. It may still be useful
       * for simpler interactions. Use in conjunction with turn_rotation to save current position.
       * @var {int} adventurejs.Tangible#turn_positions
       * @default 1
       */
      this.turn_positions = 1;

      /**
       * This property was superseded by the GraduatedController class. It may still be useful
       * for simpler interactions. Use in conjunction with turn_positions to set number of positions.
       * @var {float} adventurejs.Tangible#turn_rotation
       * @default 0
       */
      this.turn_rotation = 0;

      /**
       * @var {String} adventurejs.Tangible#turn_target_id
       * @default ""
       */
      this.turn_target_id = "";

      /**
       * Used by GraduatedController class to set target for the controller.
       * @var {String} adventurejs.Tangible#control_target_id
       * @default ""
       */
      this.control_target_id = "";

      /**
       * Used by GraduatedController class to set number of control positions for a
       * controller, where 2 is a toggle and anything more than 2 is a dial.
       * @var {int} adventurejs.Tangible#control_positions
       * @default 1
       */
      this.control_positions = 1;

      /**
       * Used by GraduatedController class to set current position of controller.
       * 0 means off. If 2 positions are available, 1 means on. If more than
       * 2 positions, each position above 0 is treated as a percent of total capacity.
       * For example in a 3 position controller, 1 = 50% and 2 = 100%;
       * @var {int} adventurejs.Tangible#current_position
       * @default 0
       */
      this.current_position = 0;

      /**
       * Set whether this asset can be swung at another asset, as in swinging a bat at a ball.
       * @var {Boolean} adventurejs.Tangible#can!be_swung_at
       * @default true
       */
      this.can.be_swung_at = true;

      // readin'n'writin'

      /**
       * Set whether this asset can be written in
       * like a book.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!write_on_means_write_in
       * @default false
       */
      this.quirks.write_on_means_write_in = false;

      /**
       * List of strings that have been written on this asset.
       * @var {Array} adventurejs.Tangible#written_strings
       * @default []
       */
      this.written_strings = [];

      /**
       * Set whether strings written on this asset are appended to its description.
       * @var {Boolean} adventurejs.Tangible#append_written_strings_to_description
       * @default false
       */
      this.append_written_strings_to_description = false;

      /**
       * Set the ID of a target asset for this asset to type on, as in a screen for a keyboard.
       * @var {String} adventurejs.Tangible#typing_target_id
       * @default ""
       */
      this.typing_target_id = "";

      // ROPES

      /**
       * Set whether this is taken into player inventory when another asset is tied
       * to it. For example, on tying a string that is in inventory to a tin can that
       * is not in inventory, it's assumed that player would then pick up the can.
       * @var {Boolean} adventurejs.Tangible#on_tie_to_this_take_this
       * @default false
       */
      this.on_tie_to_this_take_this = false;

      /**
       * If player uses a rope in inventory to tie to an object that is not in inventory,
       * this sets whether the object gets dragged behind when player leaves the current room.
       * @var {Boolean} adventurejs.Tangible#on_tie_to_drag_behind_rope
       * @default false
       */
      this.on_tie_to_drag_behind_rope = false;

      /**
       * Set whether this asset's description includes things that it is tied to.
       * @var {Boolean} adventurejs.Tangible#show_things_this_is_tied_to_in_description
       * @default true
       */
      this.show_things_this_is_tied_to_in_description = true;

      /**
       * Set whether 'take' means 'hold', as in the case of hanging ropes.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!take_means_hold
       * @default false
       */
      this.quirks.take_means_hold = false; // used for swingable things

      /**
       * Set whether 'let go of' means 'get off', as in the case of assets that
       * the player is suspended from, such as hanging from a rope.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!let_go_of_means_go_off
       * @default false
       */
      this.quirks.let_go_of_means_go_off = false;

      /**
       * Set whether 'let go of' means 'go down', as in the case of player being
       * suspended by a rope above a pit, where letting go means moving to a new Room.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!let_go_of_means_go_down
       * @default false
       */
      this.quirks.let_go_of_means_go_down = false;

      /**
       * Set properties for a variety of things a tangible asset can emit.
       * @var {Boolean} adventurejs.Tangible#emits
       * @default false
       * @todo Write logic for this.
       */
      this.emits = {
        gravity: { enabled: false, level: 0, description: "", results: null },
        light: { enabled: false, level: 0, description: "", results: null },
        heat: { enabled: false, level: 0, description: "", results: null },
        sound: { enabled: false, level: 0, description: "", results: null },
        smell: { enabled: false, level: 0, description: "", results: null },

        // liquid: { enabled: false, level: 0, description: '', results: null, id:'' },
        // gas: { enabled: false, level: 0, description: '', results: null, id:'' },
        // slurry: { enabled: false, level: 0, description: '', results: null, id:'' },
        // solid: { enabled: false, level: 0, description: '', results: null, id:'' },
        substance: {
          enabled: false,
          level: 0,
          description: "",
          results: null,
          id: "",
        },
      };

      /**
       * Set whether this asset must have a location. Chiefly for use with Aperture class.
       * Apertures must have a location because they're tied to Exits. Other assets can
       * exist without a location, though unlocated tangible assets won't be available to player.
       * @var {Boolean} adventurejs.Tangible#location_required
       * @default false
       */
      this.location_required = false; // some classes must have a location

      /**
       * Explicitly set whether this asset can exist without a location. Chiefly for use
       * with Room class. Rooms will not have a location.
       * @var {Boolean} adventurejs.Tangible#location_unneccessary
       * @default false
       */
      this.location_unneccessary = false; // some classes don't need a location

      /**
       * Set the minimum light required to see this asset. Meant for situations with
       * variable lighting where some objects might be hidden in shadow.
       * @var {float} adventurejs.Tangible#min_light_required_to_see
       * @default 0.5
       * @todo Write logic for this in selectVisible.js
       */
      this.min_light_required_to_see = 0.5; // TODO amount of ambient light required to be visible

      /**
       * @var {Object} adventurejs.Tangible#dimensions
       * @default {}
       */
      this.dimensions = {
        depth: -1,

        /**
         * Set a height for this asset. Used to calculate whether some assets can fit in other assets.
         * Also used for climbing and reachability.
         * Currently, 1 is considered to be human height, or about six feet.
         * @var {float} adventurejs.Tangible#dimensions.height
         * @default 1
         */
        height: 0,

        /**
         * Set the opacity for this asset. Used for assets made of transparent materials
         * such as glass, and helps determine whether player can look in or through assets.
         * @var {float} adventurejs.Tangible#opacity
         * @default 1
         */
        opacity: 1,

        /**
         * Set a size for this asset. Meant for figuring whether some assets can fit in other assets.
         * Logic for this is incomplete and may be duplicative of width / height / depth.
         * @var {float} adventurejs.Tangible#dimensions.size
         * @default -1
         * @todo Revisit this. Does it conflict with width / height / depth?
         */
        size: -1,

        /**
         * Set a weight for this asset. Used to calculate whether some assets can fit in other assets.
         * May also be used for buoyancy though no logic has been written for that.
         * @var {float} adventurejs.Tangible#dimensions.weight
         * @default -1
         * @todo Figure weight into buoyancy?
         */
        weight: -1,

        /**
         * Set a width for this asset. Used to calculate whether some assets can fit in other assets.
         * @var {float} adventurejs.Tangible#dimensions.width
         * @default 1
         */
        width: -1,
      };

      /**
       * XYZ coordinates. Defaults to 0,0,0.
       * It's safe to ignore these if you don't want to use them.
       * They can be used for things like:
       * <ul>
       * <li>managing reachability of objects that are on top of other things</li>
       * <li>managing reachability of objects in the room while player is climbing or standing atop a thing</li>
       * <li>dividing a room up into reachable/unreachable spaces</li>
       * <li>managing player depth in an underwater location</li>
       * <li>managing player position while flying/floating/levitating</li>
       * </ul>
       * Here's an example of how to set an object's position.
       * <pre class="display"><code class="language-javascript">MyGame.createAsset({
       *   class: "Stalactite",
       *   name: "stalactite",
       *   place: { on: "Colossal Cave" },
       *   descriptions: {look: "It clings tight to the ceiling. ",},
       *   height: -2,
       *   position: { x:0, y:5, z:0 },
       * });
       * </code></pre>
       * Also see related <a href="#height">height</a>.
       * @var {Object} adventurejs.Tangible#position
       * @default {x:0,y:0,z:0}
       *
       * @related height
       */
      this.position = {
        x: 0,
        y: 0,
        z: 0,
      };

      /**
       * Meant for handling screen output, by breaking listed items into subgroups
       * to be divided into paragraphs, but not implemented.
       * @var {int} adventurejs.Tangible#list_group
       * @default 0
       *
       * @todo Implement this or remove it.
       */
      this.list_group = 0; // @TODO list visible items in separate paragraphs

      /**
       * Set whether verb point means aim for this asset.
       * If so, point verb will redirect to aim.
       * @nestedproprty
       * @var {Boolean} adventurejs.Tangible#quirks!point_means_aim
       * @default false
       *
       */
      this.quirks.point_means_aim = false;

      /**
       * When player is nested within an aspect of an asset, other assets may not be reachable.
       * <strong>things_player_can_reach_from_this</strong> helps manage reachability.
       * For example, if player is on a ladder, things on the ground might not be reachable.
       * But, a player seated on a chair at a desk should be able to reach the desk and anything on it.
       * Use this property to explicitly make some things reachable from other things.
       * <br/><br/>
       * <em>things_player_can_reach_from_this is a broad catchall that covers all
       * nesting options.</em> What this means is that players can reach assets in this
       * list no matter how they are nested within this asset.
       * Going back to the example of the chair and desk, if the chair has this property set thusly...
       * <pre class="display"><code class="language-javascript">this.things_player_can_reach_from_this = [ 'desk' ];
       * </code></pre>
       * ...it means that the player, seated in the chair,
       * can reach any part of the desk: not just stuff that's on it, but also stuff
       * under it or behind it. That might be fine for your purposes.
       * Or, you might want more granular control. Let's say there's an electrical outlet under the desk,
       * and you want that players shouldn't be able to reach it without actually crawling under the desk.
       * If you need that kind of precision, you can manage reachability for each aspect of an asset.
       * For more information about that, see
       * {@link adventurejs.Aspect#things_player_can_reach_from_this_aspect|Aspect.things_player_can_reach_from_this_aspect}
       * and
       * {@link adventurejs.Aspect#things_player_can_reach_from_positions_of_this_aspect|Aspect.things_player_can_reach_from_positions_of_this_aspect}.
       * @var {Array} adventurejs.Tangible#things_player_can_reach_from_this
       * @default []
       *
       */
      this.things_player_can_reach_from_this = []; // defined

      /**
       * When player is nested within an asset, other assets may not be reachable.
       * For example, if player is on a ladder, things on the ground won't be reachable.
       * Use this property to explicitly list things that player can reach while nested
       * at the top of asset. For example, perhaps there is a window that player should
       * only be able to reach from the top of a ladder.
       * <strong>things_player_can_reach_from_top_of_this</strong> allows that to be set.
       * For example:
       * <pre class="display"><code class="language-javascript">this.things_player_can_reach_from_top_of_this = [ 'window' ];
       * </code></pre>
       * <em>things_player_can_reach_from_this is a convenience method for a common situation.</em>
       * If you need more control over reachability from specific positions within an aspect,
       * like left/right/front/back, you can manage that at the aspect level.
       * For more information about that, see
       * {@link adventurejs.Aspect#things_player_can_reach_from_this_aspect|Aspect.things_player_can_reach_from_this_aspect}
       * and
       * {@link adventurejs.Aspect#things_player_can_reach_from_positions_of_this_aspect|Aspect.things_player_can_reach_from_positions_of_this_aspect}.
       * @var {Array} adventurejs.Tangible#things_player_can_reach_from_top_of_this
       * @default []
       *
       */
      this.things_player_can_reach_from_top_of_this = []; // defined

      /**
       * When player is nested within an asset, other assets may not be reachable.
       * For example, if player is on a ladder, things on the ground won't be reachable.
       * Use this property to explicitly list things that player can reach while nested
       * at the bottom of asset. For example, perhaps there is an alcove at the bottom
       * of a pit, with a ladder leading down, where player should
       * only be able to reach the alcove from the bottom of the ladder.
       * <strong>things_player_can_reach_from_bottom_of_this</strong> allows that to be set.
       * For example:
       * <pre class="display"><code class="language-javascript">this.things_player_can_reach_from_bottom_of_this = [ 'alcove' ];
       * </code></pre>
       * <em>things_player_can_reach_from_bottom_of_this is a convenience method for a common situation.</em>
       * If you need more control over reachability from specific positions within an aspect,
       * like left/right/front/back, you can manage that at the aspect level.
       * For more information about that, see
       * {@link adventurejs.Aspect#things_player_can_reach_from_this_aspect|Aspect.things_player_can_reach_from_this_aspect}
       * and
       * {@link adventurejs.Aspect#things_player_can_reach_from_positions_of_this_aspect|Aspect.things_player_can_reach_from_positions_of_this_aspect}.
       * @var {Array} adventurejs.Tangible#things_player_can_reach_from_top_of_this
       * @default []
       *
       */
      this.things_player_can_reach_from_bottom_of_this = []; // defined

      /**
       * Some Tangible subclasses come pre-coded to handle certain
       * "parts", classes which can automatically be registered with
       * each other to form complex associations.
       * For example, a
       * {@link adventurejs.Sink|Sink}
       * can have matching
       * {@link adventurejs.Faucet|Faucet},
       * {@link adventurejs.FaucetHandle|FaucetHandles},
       * {@link adventurejs.Drain|Drain} and
       * {@link adventurejs.Plug|Plug}.
       * Not all classes have parts. See each class's documentation
       * header for "Can have parts:" and "Can be part of:".
       * The registerParts() method is called during initialization.
       * Set up parts like so:
       * <pre class="display"><code class="language-javascript">MyGame.createAsset({
       *   class: "Sink",
       *   name: "sink",
       *   place: { in: "Bathroom" },
       *   descriptions:{
       *     look: function()
       *     {
       *       return "A pedestal sink with porcelain handles and
       *       a stainless steel faucet. Its drain appears to be
       *       $( sink drain is| open or| closed ). ";
       *     }
       *   },
       *   parts: [
       *     // each of these is a name of another Asset
       *     "hot water handle",
       *     "cold water handle",
       *     "faucet",
       *     "drain",
       *     "plug"
       *   ],
       * });
       * </code></pre>
       * @var {Array} adventurejs.Tangible#parts
       * @default []
       *
       */
      this.parts = [];

      /**
       *
       * @var {Object} adventurejs.Tangible#registered_parts
       * @default {}
       *
       */
      this.registered_parts = {};

      /**
       *
       * @var {Object} adventurejs.Tangible#registerableClasses
       * @default {}
       *
       */
      this.registerableClasses = {};

      this.place = {};
    } // function Tangible(id)

    // PROTOTYPE DEFINED PROPERTIES

    /*doc@this*/
    get parts() {
      return this.__parts;
    }
    set parts(arr) {
      this.__parts = A.validateAssetList(arr);
    }

    /*doc@this*/
    get things_player_can_do_all_verbs_to_from_this() {
      return this.__things_player_can_do_all_verbs_to_from_this;
    }
    set things_player_can_do_all_verbs_to_from_this(arr) {
      this.__things_player_can_do_all_verbs_to_from_this =
        A.validateAssetList(arr);
    }

    /*doc@this*/
    get things_player_can_climb_to_from_this() {
      return this.__things_player_can_climb_to_from_this;
    }
    set things_player_can_climb_to_from_this(arr) {
      this.__things_player_can_climb_to_from_this = A.validateAssetList(arr);
    }

    /*doc@this*/
    get things_player_can_jump_to_from_this() {
      return this.__things_player_can_jump_to_from_this;
    }
    set things_player_can_jump_to_from_this(arr) {
      this.__things_player_can_jump_to_from_this = A.validateAssetList(arr);
    }

    /**
     * @var {Getter/Setter} adventurejs.Tangible#things_player_can_swing_to_across_this
     * @default false
     */
    get things_player_can_swing_to_across_this() {
      return this.__things_player_can_swing_to_across_this;
    }
    set things_player_can_swing_to_across_this(arr) {
      this.__things_player_can_swing_to_across_this = A.validateAssetList(arr);
    }

    /*doc@this*/
    get things_player_can_swing_to_from_this() {
      return this.__things_player_can_swing_to_from_this;
    }
    set things_player_can_swing_to_from_this(arr) {
      this.__things_player_can_swing_to_from_this = A.validateAssetList(arr);
    }

    /**
     * Get a string representing open / closed state of this asset.
     * @var {Boolean} adventurejs.Tangible#print_open_or_closed
     * @default false
     */
    get print_open_or_closed() {
      var state = "neither open nor closed";
      if (this.is.closed) state = "closed";
      if (false === this.is.closed) state = "open";
      return state;
    }

    /**
     * Get / set place. The private var __place is an
     * object with two properties: aspect and asset.
     * For example: { aspect:"in", asset:"room" }
     * However, the public var place appears in the form
     * { in: "room" }. This is to make it easier and more
     * intuitive for authors to set asset places.
     * @var {Object} adventurejs.Tangible#place
     */
    get place() {
      return { [this.__place.aspect]: this.__place.asset };
    }
    set place(value) {
      var newplace = { asset: "", aspect: "" };
      if (Object(value) !== value) {
        var msg = this.id + ".place received no value. Setting no place. ";
        this.game.log("warn", "critical", msg, "Tangible");
      } else {
        // for some reason world.copy was returning {"":"","key":"value"}
        // so we need to delete the empty string key
        delete value[""];

        var keys = Object.keys(value);
        if (value.asset && value.aspect) {
          // received for example: { aspect:"in", asset:"room" }
          // serialize asset name
          newplace.aspect = value.aspect;
          newplace.asset = A.serialize(value.asset);
        } else if (keys.length === 0) {
        } else if (keys.length === 1) {
          // received for example: { in: "room" }
          // verify asset
          if ("string" !== typeof value[keys[0]]) {
            var msg = this.id + ".place set to an invalid asset ";
            this.game.log("error", "critical", msg, "Tangible");
          }
          // serialize asset name
          newplace.asset = A.serialize(value[keys[0]]);
          newplace.aspect = keys[0];
        } else if (keys.length > 1) {
          newplace.asset = value[keys[0]];
          newplace.aspect = keys[0];
          var msg =
            this.id +
            ".place received more than one location. Using the first. ";
          for (var i = 0; i < keys.length; i++) {
            msg += keys[i] + ": " + value[keys[i]] + ", ";
          }
          this.game.log("error", "critical", msg, "Tangible");
        }
      }

      if (
        this.__place &&
        this.__place.asset &&
        this.__place.aspect &&
        (this.__place.asset !== newplace.asset ||
          this.__place.aspect !== newplace.aspect)
      ) {
        this.game
          .getAsset(this.__place.asset)
          .removeAssetAt(this.id, this.__place.aspect);
      }
      if (
        newplace.asset &&
        newplace.aspect &&
        this.game.getAsset(newplace.asset)
      ) {
        this.game.getAsset(newplace.asset).addAssetAt(this.id, newplace.aspect);
      }
      this.__place = newplace;
    }

    /*doc@this*/
    get things_player_can_reach_from_this() {
      return this.__things_player_can_reach_from_this;
    }
    set things_player_can_reach_from_this(arr) {
      this.__things_player_can_reach_from_this = A.validateAssetList(arr);
    }

    /*doc@this*/
    get things_player_can_reach_from_top_of_this() {
      return this.__things_player_can_reach_from_top_of_this;
    }
    set things_player_can_reach_from_top_of_this(arr) {
      this.__things_player_can_reach_from_top_of_this =
        A.validateAssetList(arr);
    }

    /*doc@this*/
    get things_player_can_reach_from_bottom_of_this() {
      return this.__things_player_can_reach_from_bottom_of_this;
    }
    set things_player_can_reach_from_bottom_of_this(arr) {
      this.__things_player_can_reach_from_bottom_of_this =
        A.validateAssetList(arr);
    }

    /**
     * <strong>Contains</strong> is a shortcut for creating
     * a substance container and filling it with an infinite
     * amount of a specified substance.
     * It is the equivalent of this code:
     * <pre class="display"><code class="language-javascript">this.aspects.in = new adventurejs.Aspect( "in", this.game_name )
     *   .set({
     *     "parent_id": this.id,
     *   });
     * this.aspects.in.vessel = new adventurejs.Vessel( "in", game_name )
     *   .set({
     *     "volume": Infinity,
     *     "maxvolume": Infinity,
     *     "substance_id": substance_id,
     *   });
     * </code></pre>
     * <br><br>
     * This is to make it easier and more
     * intuitive for authors to set things like sand
     * in a desert room or water in a swamp room, so that
     * if player inputs "fill bowl with water", it can be
     * assumed that the room is the source of the substance.
     * When multiple substance containers are available,
     * usually disambiguation occurs, but in the case of a
     * room containing a substance, the room is assumed to
     * be the source.
     * @var {Object} adventurejs.Tangible#place
     */
    get contains() {
      if (this.hasVesselAtAspect("in")) {
        return this.aspects.in.vessel.substance_id;
      }
      return "";
    }
    set contains(params) {
      if ("string" === typeof params) {
        params = { substance_id: params };
      }
      if (!params) params = {};
      if (!params.substance_id) {
        params.substance_id = "";
      }
      if (!params.maxvolume) params.maxvolume = Infinity;
      if (!params.volume) params.volume = Infinity;
      params.vessel_is_known = true;
      this.setVesselAt("in", params);
    }

    // METHODS

    /**
     * Inherited from superclass {@link adventurejs.Asset|Asset}.
     * Tangible adds validation methods that are used for all Tangible assets,
     * including:
     * <ul>
     * <li>check for implied dependencies and make them explicit</li>
     * <li>check for proper asset location</li>
     * <li>set parent associations</li>
     * </ul>
     *
     * @memberOf adventurejs.Tangible
     * @method adventurejs.Tangible#validate
     * @param {Object} game
     * @returns {Boolean}
     */
    validate(game) {
      super.validate(game);

      // Validate place. Many tangibles, generally globals, have no place,
      // and that's valid, but if it does have a place, ensure that it refers
      // to a valid asset.
      if (this.__place.asset) {
        var place_asset, place_aspect;
        place_aspect = Object.keys(this.place)[0];
        place_asset = this.game.getAsset(A.serialize(this.__place.asset));

        // is place_object a tangible game asset?
        if (!place_asset || !(place_asset instanceof adventurejs.Tangible)) {
          var msg = `${this.constructor.name} ${this.name}'s place ${this.__place.asset} is unset or invalid. `;
          this.game.log("error", "critical", msg, "Tangible");
          return false;
        }

        // has place had an Aspect instantiated?
        if (
          !place_asset.hasAspectAt(place_aspect) ||
          !place_asset.getAspectAt(place_aspect).class
        ) {
          place_asset.aspects[place_aspect] = new adventurejs.Aspect(
            place_aspect,
            this.game_name
          ).set({
            parent_id: place_asset.id,
          });
          this.game.debug(
            `F1568 | Tangible.js | ${this.constructor.name} ${this.name}'s container, ${place_asset.name}.${place_aspect}, was not set. A new Aspect has been constructed for ${place_asset.name}.${place_aspect}. `
          );
          this.game.print(msg);
        }
      }

      // has it got no location but requires a location?
      if (this.location_required && !this.hasPlace()) {
        msg += this.constructor.name + " " + this.name + " hasn't got a place.";
        console.error(msg);
        return false;
      }

      return true;
    } // validate

    /**
     * Inherited from superclass {@link adventurejs.Asset|Asset}.
     * Tangible adds initialization methods that are used for all
     * Tangible assets, including:
     * <ul>
     * <li>link related objects</li>
     * <li>register parts</li>
     * </ul>
     * @memberOf adventurejs.Tangible
     * @method adventurejs.Tangible#initialize
     * @param {Object} game
     * @returns {Boolean}
     */
    initialize(game) {
      super.initialize(game);
      if (this.getPlaceAsset()) {
        this.getPlaceAsset().addAssetAt(this.id, this.getPlacePreposition());
      }
      this.registerParts.call(this);
      this.linkRegisteredParts.call(this);
      return true;
    } // p.initialize

    /**
     * Remove this asset from the world before calling superclass.destroy.
     * @memberOf adventurejs.Tangible
     * @method adventurejs.Tangible#destroy
     */
    destroy() {
      this.setPlace(); // calling without param removes from parent

      // in most cases we call the super method first
      // in the case of destroy the last thing to happen
      // is removing from lookups
      super.destroy();
    }
  }
  adventurejs.Tangible = Tangible;
})();