Pre-release
AdventureJS Docs Downloads
Score: 0 Moves: 0
// Aspect.js
(function () {
  /* global adventurejs A */

  /**
   * @ajspath adventurejs.Atom.Aspect
   * @augments adventurejs.Atom
   * @class adventurejs.Aspect
   * @ajsnavheading BaseClasses
   * @param {String} game_name Name of top level game instance that is scoped to window.
   * @param {String} name Instance name.
   * @summary Class that allows putting things in/on/under/behind/attached.
   * @tutorial Tangibles_Aspects
   * @classdesc
   * <p>
   * <strong>Aspect</strong> is a special class
   * that creates spaces within any
   * {@link adventurejs.Tangible|Tangible}
   * {@link adventurejs.Asset|Asset},
   * which can contain other Tangibles and/or
   * {@link adventurejs.Substance|Substances},
   * with the addition of a
   * {@link adventurejs.Vessel|Vessel}.
   * The five most commonly used aspects are behind,
   * in, on, under, and attached, and a lot of default logic is
   * predicated on using one of these. However it is possible to
   * create aspects at any preposition. Just note that it might
   * lead to unexpected results and require custom code.
   * </p>
   * <pre class="display"><code class="language-javascript">this.aspects.behind = {};
   * </code></pre>
   * <p>
   * Here is an example of how to set the properties of a
   * Aspect of an existing class using createAsset.
   * If you use a preposition that hasn't been defined for the class
   * you're using, a new Aspect will be constructed
   * automatically during construction.
   * </p>
   * <pre class="display"><code class="language-javascript">MyGame.createAsset({
   *   class: "Desk",
   *   name: "desk",
   *   place: { in: "Office" },
   *   behind: {
   *     list_contents_in_room: false,
   *     list_contents_in_examine: true,
   *     contents_limits: {
   *       height: 1,
   *       width: 6,
   *       depth: 4,
   *     },
   *   }
   * });
   * </code></pre>
   * <p>
   * To define a new class with an Aspect, use the Aspect
   * constructor within the class constructor. Here is a very simple
   * example of a new class with a behind Aspect.
   * </p>
   * <pre class="display"><code class="language-javascript">class NewClass {
   *   constructor( name, game_name ) {
   *     super( name, game_name );
   *     this.aspects.newaspect = new adventurejs.Aspect( "behind", this.game_name, this.id )
   *     .set({
   *       // optional params
   *     });
   *    }
   *   }
   *   adventurejs.NewClass = NewClass;
   * };
   * </code></pre>
   **/
  class Aspect extends adventurejs.Atom {
    constructor(name, game_name, context_id) {
      super(name, game_name, context_id);
      this.class = "Aspect";
      this.preposition = name;
      this.id = `${context_id}|${name}|aspect`;

      this.TYPES = {
        in: "container",
        on: "surface",
        under: "relative",
        behind: "relative",
        over: "relative",
        attached: "connection",
      };
      this.type = this.TYPES[this.id];

      /**
       * When the player enters a room, items in the room are
       * listed in the room's description, and the contents of
       * those items may or may not be listed as well, depending
       * on this setting. For example, consider a bed with pillows
       * on it: "There is a bed here. On the bed you see some pillows."
       * Now consider the monster under the bed: "There is a bed here.
       * Under the bed you see a monster." To prevent the monster from
       * being revealed in the room description, simply set
       * bed.aspects.under.list_contents_in_room to false.
       * @var {boolean} adventurejs.Aspect#list_contents_in_room
       * @default true
       */
      this.list_contents_in_room = true;

      /**
       * When an asset is examined, authors may control whether
       * aspect contents are listed in the description. Consider
       * a pocket protector: examining it might list the pens and
       * pencils in it. On the other hand, consider a desk with
       * drawers: if the drawers are part of the desk's general
       * description, the author might not want them listed.
       * @var {boolean} adventurejs.Aspect#list_contents_in_examine
       * @default true
       */
      this.list_contents_in_examine = true;

      /**
       * This property allows authors some control over when asset
       * contents become known. Generally speaking, assets are unknown
       * until they have been "seen", which usually occurs when the
       * player enters a room containing the asset.
       * When an asset becomes known, it may or may not be desirable
       * for its contents to become known. For example, items on a
       * desk should probably be known, whereas items under a bed
       * might better remain unknown until the player tries "look under bed".
       * Or consider a knapsack: if it's closed, the player might see
       * the knapsack, but shouldn't know what's inside of it until they open it.
       * @var {boolean} adventurejs.Aspect#know_contents_with_parent
       * @default true
       */
      this.know_contents_with_parent = true;

      /**
       * This property allows authors some control over when asset
       * contents become seen. Generally speaking, assets are unseen
       * until they have been "seen", which usually occurs when the
       * player enters a room containing the asset.
       * When an asset becomes seen, it may or may not be desirable
       * for its contents to become seen. For example, items on a
       * desk should probably be seen, whereas items under a bed
       * might better remain unseen until the player tried "look under bed".
       * Or consider a knapsack: if it's closed, the player might see
       * the knapsack, but shouldn't see what's inside of it until they open it.
       * Known and seen are distinct properties because it's possible
       * to know about an asset without seeing it.
       * @var {boolean} adventurejs.Aspect#see_contents_with_parent
       * @default true
       */
      this.see_contents_with_parent = true;

      /**
       * A list of asset ids that are contained in this aspect.
       * @var {Array} adventurejs.Aspect#contents
       * @default []
       */
      this.contents = []; // defined

      /**
       * Set maximums for contents of dimensions, count, and volume.
       * -1 means infinite.
       * @var {Array} adventurejs.Aspect#contents_limits
       * @default { height: -1, width: -1, depth: -1, count: -1, weight: -1, }
       */
      this.contents_limits = {
        height: -1,
        width: -1,
        depth: -1,
        count: -1,
        weight: -1,
      };

      /**
       * Objects that are taller than player height may be scaled
       * (ie climbed, though the action is not exclusive to climb verb).
       * This var determines how far the player climbs per turn. For example,
       * a tree with height of 5 and scale_increment of 1
       * will take the player 5 turns of climbing to reach the top, whereas
       * a scale_increment of 5 will let players climb the tree in 1 turn.
       * The default scale_increment of -1 will let players climb an object
       * in 1 turn regardless of height.
       * @var {Array} adventurejs.Aspect#scale_increment
       * @default -1
       */
      this.scale_increment = -1;

      /**
       * A list of asset ids that are allowed to be contained in this aspect.
       * Use this to limit what things can be put in other things. For
       * example, consider a scabbard which only allows one particular sword
       * to be put in it.
       * @var {Array} adventurejs.Aspect#with_assets
       * @default []
       */
      this.with_assets = []; // defined

      /**
       * A list of classes that are allowed to be contained in this aspect.
       * Use this to limit what things can be put in other things. For
       * example, consider a pocket protector, which only allows pens and
       * pencils to be put in it.
       * @var {Array} adventurejs.Aspect#with_classes
       * @default []
       */
      this.with_classes = []; // defined

      /**
       * Determine whether the player can add assets to this aspect's contents.
       * @var {boolean} adventurejs.Aspect#player_can_add_assets_to_contents
       * @default true
       */
      this.player_can_add_assets_to_contents = true;

      /**
       * Determine whether the player can remove assets from this aspect's contents.
       * @var {boolean} adventurejs.Aspect#player_can_remove_assets_from_contents
       * @default true
       */
      this.player_can_remove_assets_from_contents = true;

      /**
       * Vessels allow aspects to contain substances.
       * <a href="adventurejs.Vessel.html">Vessel</a> is a distinct class
       * with its own methods and properties for managing substances.
       * In theory, any aspect can contain substances: but in practice,
       * only in aspects are used. For example, consider a pile of dust
       * under a bed: rather than adding the dust to bed.aspects.under.vessel,
       * you would create a dustpile asset and add dust to dustpile.aspects.in.vessel.
       * @var {Object} adventurejs.Aspect#vessel
       * @default {}
       */
      this.vessel = {};

      /**
       * Nests allow aspects to contain characters.
       * <a href="adventurejs.Vessel.html">Vessel</a> is a distinct class
       * with its own methods and properties for managing substances.
       * A collection of properties that defines whether a player may
       * enter this aspect, what actions they are allowed to perform
       * in it, and the default posture they will take upon entering.
       * Aspects and Rooms both share these properties. To initialize a nest:
       * <pre class="display"><code class="language-javascript">this.nest = new adventurejs.Nest(
       *   "nest",
       *   this.game_name,
       *   this.context_id
       * ).set({
       *   preposition: this.name,
       * });
       * </code></pre>
       * @var {Object} adventurejs.Aspect#nest
       * @default {}
       */
      this.nest = {};

      /**
       * <strong>orientation</strong> determines whether an Aspect
       * is horizontal or vertical. Chiefly meant to distinguish
       * between player being on a flat surface such as a table,
       * vs player being on a vertical face such as a tree.
       * @var {String} adventurejs.Aspect#orientation
       * @default "horizontal"
       */
      this.orientation = "horizontal";

      return this;
    }

    get contents() {
      return this._contents;
    }
    set contents(arr) {
      if (!Array.isArray(this._contents)) {
        this._contents = [];
      }
      this._contents = A.validateAssetList(arr);
    }

    get with_assets() {
      return this._with_assets;
    }
    set with_assets(arr) {
      if (!Array.isArray(this._with_assets)) {
        this._with_assets = [];
      }
      this._with_assets = A.validateAssetList(arr);
    }

    get with_classes() {
      return this._with_classes;
    }
    set with_classes(arr) {
      if (!Array.isArray(this._with_classes)) {
        this._with_classes = [];
      }
      this._with_classes = A.validateClassList(arr);
    }

    get number_of_listable_things() {
      var num = this.contents.length;
      for (var i = num - 1; i > -1; i--) {
        if (!this.game.getAsset(this.contents[i]).is.listed) {
          num--;
        }
      }
      return num;
    }

    /**
     * For the most part the answer is yes. It might not be for <code>in</code>.
     * @todo reachability
     */
    canPlayerReachThisContents() {
      //
    }

    /**
     * For the most part the answer is yes. It might not be for <code>in</code>.
     * @todo visibility
     */
    canPlayerSeeThisContents() {
      //
    }

    /**
     * canCharacter(property) returns aspect.nest.can[property] or false.
     * @param {*} property A property to test.
     * @returns {Boolean}
     */
    canCharacter(property) {
      if (!this.nest || !this.nest.can) return false;
      return this.nest.can[property] || false;
    }

    canThisHoldThat(asset) {
      let response = { fail: false, msg: "", status: "", return: undefined };
      let msg = "";
      let can = true;
      if ("string" === typeof asset) {
        asset = this.game.getAsset(asset);
      }
      if (!asset || !(asset instanceof adventurejs.Tangible)) {
        this.game.debug(
          ` | Verb.js via ${this.name}.js | received bad request `
        );
        msg += this.game.settings.getUnparsedMessage(
          this.game.getInput().input
        );
        response = {
          fail: true,
          msg: msg,
          status: "bad_request",
          return: false,
        };
        return response;
      }
    }
  }
  adventurejs.Aspect = Aspect;
})();