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

  /**
   * @augments adventurejs.Thing
   * @class adventurejs.Aperture
   * @ajsconstruct MyGame.createAsset({ "class":"Aperture", "name":"foo", [...] })
   * @ajsconstructedby adventurejs.Game#createAsset
   * @ajsnavheading DoorExitClasses
   * @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 doors, windows, and other passageways.
   * @tutorial CreateExit
   * @classdesc
   * <p>
   * <strong>Aperture</strong> is the base class for all
   * {@link adventurejs.Door|Doors},
   * {@link adventurejs.Window|Windows},
   * and other types of passageway. An Aperture is
   * always associated with an {@link adventurejs.Exit|Exit}.
   * Because Exits have no physical properties of their own,
   * Apertures provide a way to add those missing
   * manipulatable physical properties.
   * Each Aperture only exists in one {@link adventurejs.Room|Room}
   * and is one-way. To make a two-way passage, create
   * two Apertures, one in each Room, and set each Aperture's
   * <a href="#linked_asset">linked_asset</a>
   * property to its mate. This allows them to share state,
   * i.e., unlocking one side also unlocks the other.
   * This example shows two Rooms with a two-way passage between them.
   * </p>
   * <h3 class="example">Example:</h3>
   * <pre class="display"><code class="language-javascript">MyGame.createAsset({
   *   "class":"Exit",
   *   "direction":"north",
   *   "place":{in:"South Room"},
   *   "destination":"North Room",
   *   "aperture":"icy door"
   * });
   * MyGame.createAsset({
   *   "class":"Door",
   *   "name":"icy door",
   *   "place":{in:"South Room"},
   *   "direction":"north",
   *   "linked_asset":"warm door"
   * });
   * MyGame.createAsset({
   *   "class":"Exit",
   *   "direction":"south",
   *   "place":{in:"North Room"},
   *   "destination":"South Room",
   *   "aperture":"warm door"
   * });
   * MyGame.createAsset({
   *   "class":"Door",
   *   "name":"warm door",
   *   "place":{in:"North Room"},
   *   "direction":"south",
   *   "linked_asset":"icy door"
   * });
   * </code></pre>
   *
   **/
  class Aperture extends adventurejs.Thing {
    constructor(name, game_name) {
      super(name, game_name);
      this.class = "Aperture";

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

      this.unsetDOV("take");
      this.setDOVs([
        "open",
        "close" /*,'lock','unlock','seal','unseal','pick'*/,
      ]);

      /**
       * Set a direction for this asset. Chiefly used with Aperture class to set
       * directions apertures. Takes a direction string, ie "north", "northeast", "down".
       * @var {String} adventurejs.Tangible#direction
       * @default ""
       * @todo Use lookup table for this? In a GUI this would be a pull-down menu.
       */
      this.direction = ""; // for use with apertures

      /**
       * Set an ID representing an Exit. Used chiefly for Aperture class to set an
       * exit corresponding to an aperture.
       * @var {String} adventurejs.Tangible#exit
       * @default ""
       */
      this.exit = ""; // for use with apertures

      this.location_required = true;
      // ex: this.direction = "north"; // an aperture will be associated with an exit direction

      this.can.auto_unlock = true;
      this.can.auto_unseal = true;
      this.can.auto_open = true;

      /**
       * ID of an Asset that is the other side of this. Used
       * for connecting doors.
       * @var {String} adventurejs.Aperture#linked_asset
       * @default ""
       */
      //this.linked_asset = "";
    }

    initialize(game) {
      super.initialize(game);

      var words = [];

      words.push(this.direction);
      var directionConstructor =
        this.direction + " " + this.constructor.name.toLowerCase();
      words.push(directionConstructor);
      words.push(A.serialize(directionConstructor));

      var directionName = this.direction + " " + this.name.toLowerCase();
      words.push(directionName);
      words.push(A.serialize(directionName));

      while (words.length > 0) {
        if (!this.game.world_lookup[words[0]]) {
          this.game.world_lookup[words[0]] = {};
          this.game.world_lookup[words[0]].IDs = [];
          this.game.world_lookup[words[0]].type = "direction";
        }
        if (-1 === this.game.world_lookup[words[0]].IDs.indexOf(this.id)) {
          this.game.world_lookup[words[0]].IDs.push(this.id);
        }
        words.shift();
      }

      return this;
    }

    validate(game) {
      super.validate(game);

      var msg = "";

      if (this.linked_asset) {
        var linked_asset = this.game.getAsset(this.linked_asset);

        if (!(linked_asset instanceof adventurejs.Aperture)) {
          msg = `${this.constructor.name} ${this.name}'s linked_asset, ${this.linked_asset}, is not an Aperture. `;
          this.game.log("error", "critical", msg, "Tangible");
          return false;
        }

        // we reset linked_asset, known, and seen because these
        // also set the properties of linked assets, and
        // though this would have been called during construction,
        // the linked asset might not yet have been constructed
        // to receive the linkage
        //this.linked_asset = this.linked_asset;
        // this.is.known = this.is.known;
        // this.is.seen = this.is.seen;
        // this.is.closed = this.is.closed;
        // this.is.locked = this.is.locked;
        // this.is.sealed = this.is.sealed;
      }

      // this check was already performed in tangible.validate
      // but it's optional in tangible whereas aperture must have a place
      if (!this.hasPlace()) {
        msg = `${this.constructor.name} ${this.name}'s place isn't set. `;
        this.game.log("error", "critical", msg, "Tangible");
        return false;
      }

      // check the aperture's direction
      // aperture must have a direction
      if (!this.direction) {
        msg = `${this.constructor.name} ${this.name} has no direction. `;
        this.game.log("error", "critical", msg, "Tangible");
        return false;
      }

      // this.place will have been validated in tangible.validate
      var exitObject = this.game.getAsset(
        this.place[Object.keys(this.place)[0]] + "_" + this.direction
      );

      //if( "undefined" === typeof exitObject )
      if (Object(exitObject) !== exitObject) {
        msg = `${this.constructor.name} ${this.name}'s place + direction do not match an exit. `;
        this.game.log("error", "critical", msg, "Tangible");
        return false;
      }

      if (!(exitObject instanceof adventurejs.Exit)) {
        msg = `${this.constructor.name} ${this.name}'s place + direction matches ${exitObject.name} which is a ${exitObject.constructor.name} rather than an Exit. `;
        this.game.log("error", "critical", msg, "Tangible");
        return false;
      }

      // BOOM!
      // looks like we have a valid exit
      this.exit = exitObject.id;

      // make sure our exit refers back to this
      // depending on order validated, the exit might already have it
      //this.exit.aperture = this.id;
      //this.game.world[this.exit].aperture = this.id;
      exitObject.aperture = this.id;
    }

    /*doc@this*/
    get linked_asset() {
      return this._linked_asset;
    }
    set linked_asset(value) {
      var oldasset, newasset;
      value = A.serialize(value);
      if (this._linked_asset && this._linked_asset !== value) {
        // we have to inform the old asset
        oldasset = this.game.getAsset(this._linked_asset);
        oldasset._linked_asset = "";
      }
      newasset = this.game.getAsset(value);
      if (newasset) {
        // inform the new asset
        newasset._linked_asset = this.id;
      }
      this._linked_asset = value;
    }
  }
  adventurejs.Aperture = Aperture;
})();