Pre-release
Adventure.js Docs Downloads
Score: 0 Moves: 0
// tryTravel.js

(function () {
  /*global adventurejs A*/
  "use strict";

  var p = adventurejs.Game.prototype;

  /**
   * Tries to move the player in the specified direction.
   * @memberOf adventurejs.Game
   * @method adventurejs.Game#tryTravel
   * @param {String} direction
   * @param {Object} params
   */
  p.tryTravel = function Game_tryTravel(direction, params) {
    this.game.log("log", "high", "tryTravel.js > " + direction, "Travel");
    var input = this.game.getInput();
    var currentRoom = this.getCurrentRoom();
    var player = this.game.getPlayer();
    var nest_parent_object = player.getNestAsset();
    var nest_preposition = player.getNestPreposition();
    var exitID = currentRoom.exits[direction]; // may be no exit
    var msg = "";
    var results;
    if ("undefined" === typeof params) params = {};

    // TODO: constraint message
    if (player.is.constrained) {
      this.game.debug(`F1103 | tryTravel.js |player.is.constrained`);
      msg +=
        A.getSAF.call(this.game, player.constrained_msg) ||
        "$(We) can't go anywhere. ";
      if (msg) this.game.print(msg, input.output_class);
      return null;
    }

    // player is holding something like a rope or a railing
    if (player.IOVgetConnectionCount("hold") > 0) {
      this.game.debug(
        `F1133 | tryTravel.js | ${player.id}.iov.hold.with_params.connections.length is ${player.iov.hold.with_params.connections.length}`
      );
      msg += `$(We'll) have to let go of ${this.game.getPrintableObjectList({
        objects: player.IOVgetConnections("hold"),
      })}. `;
      if (msg) this.game.print(msg, input.output_class);
      return null;
    }

    // is player nested in something?
    if (player.isNested()) {
      // by default, can't reach an exit while nested
      // but there are exceptions
      var canReach = false;
      var reachableThings;

      if (nest_parent_object.is.rideable) {
        canReach = true;
      }

      // is player nested behind/in/on/under something
      // that can reach this direction?
      if (nest_parent_object.hasAspectAt(nest_preposition)) {
        reachableThings =
          nest_parent_object.aspects[nest_preposition]
            .things_player_can_reach_from_this_aspect;
        // @todo this no longer works - need to call canPlayerReach( direction )
        if (-1 < reachableThings.indexOf(direction)) {
          canReach = true;
        }
      }

      // is player nested on top of something that can reach this direction?
      if (!canReach && player.isNestedOnTop()) {
        // try the convenience prop first
        reachableThings =
          nest_parent_object.things_player_can_reach_from_top_of_this;
        if (true === A.isIdInMixedArray(direction, reachableThings))
          canReach = true;
        // if not, try the aspect positions
        if (!canReach) {
          reachableThings =
            nest_parent_object.aspects[nest_preposition]
              .things_player_can_reach_from_positions_of_this_aspect.top;
          if (true === A.isIdInMixedArray(direction, reachableThings))
            canReach = true;
        }
      }

      // is player nested on bottom of something that can reach this direction?
      if (!canReach && player.isNestedOnBottom()) {
        // try the convenience prop first
        reachableThings =
          nest_parent_object.things_player_can_reach_from_bottom_of_this;
        if (true === A.isIdInMixedArray(direction, reachableThings))
          canReach = true;
        // if not, try the aspect positions
        if (!canReach) {
          reachableThings =
            nest_parent_object.aspects[nest_preposition]
              .things_player_can_reach_from_positions_of_this_aspect.bottom;
          if (true === A.isIdInMixedArray(direction, reachableThings))
            canReach = true;
        }
      }

      if (false === canReach) {
        this.game.debug(
          `F1134 | tryTravel.js | ${direction} is unreachable from ${nest_preposition} ${nest_parent_object.id}`
        );
        msg += `$(We'll) have to get ${player.getPrettyUnnestPreposition()} ${
          nest_parent_object.articlename
        } first. `;
        if (msg) this.game.print(msg, input.output_class);
        return null;
      }
    }

    // does this room have an exit in the specified direction?
    if ("undefined" === typeof exitID) {
      this.game.debug(`F1115 | tryTravel.js |exitID is undefined`);

      //var directionObject = this.game.world[ "global_" + direction ];
      var direct_object = this.game.world["global_" + direction];

      // console.warn( "global!" );
      // console.warn( direct_object.id );
      var global_description;
      var currentRoom_scenery_object =
        currentRoom.room_scenery[direct_object.id];
      var currentRoomZone = this.game.world[currentRoom.zone];
      var currentRoomZone_scenery_object;
      if ("undefined" !== typeof currentRoomZone) {
        currentRoomZone_scenery_object =
          currentRoomZone.zone_scenery[direct_object.id];
      }

      // try to get current room's scenery settings
      // description can be left undefined
      if (
        "undefined" !== typeof currentRoom_scenery_object &&
        true === currentRoom_scenery_object.enabled &&
        "undefined" !== typeof currentRoom_scenery_object.description
      ) {
        // console.warn( "USE CURRENT ROOM SCENERY OBJECT DESCRIPTION" );
        this.game.debug(
          `F1116 | tryTravel.js |use room.room_scenery description for ${currentRoom.id}.room_scenery.${direct_object.id}`
        );
        global_description = currentRoom_scenery_object.description;
      }

      // otherwise try to get current room's zone scenery settings
      else if (
        "undefined" !== typeof currentRoomZone &&
        "undefined" !== typeof currentRoomZone_scenery_object &&
        true === currentRoomZone_scenery_object.enabled &&
        "undefined" !== typeof currentRoomZone_scenery_object.description
      ) {
        this.game.debug(
          `F1117 | tryTravel.js |use room.zone.zone_scenery description for ${currentRoom.zone}.zone_scenery.${direct_object.id}`
        );
        // console.warn( "USE CURRENT ROOM ZONE SCENERY OBJECT DESCRIPTION" );
        global_description = currentRoomZone_scenery_object.description;
      }

      // otherwise if room enables object,
      // try to get object's native (aka global) description
      else if (
        "undefined" !== typeof currentRoom_scenery_object &&
        true === currentRoom_scenery_object.enabled &&
        "undefined" !== typeof direct_object.description
      ) {
        this.game.debug(
          `F1118 | tryTravel.js |use room.room_scenery description for ${direct_object.id}.description`
        );
        // console.warn( "USE GLOBAL ASSET DESCRIPTION" );
        global_description = direct_object.description;
      }

      if ("undefined" !== typeof global_description) {
        msg += A.getSAF.call(this.game, global_description);
      }

      if (
        this.settings.when_travel_fails_list_exits ||
        this.world[this.world._currentRoom].when_travel_fails_list_exits
      ) {
        this.game.debug(
          `F1120 | tryTravel.js | ${this.game_name}.settings.when_travel_fails_list_exits or ${currentRoom.id}.when_travel_fails_list_exits`
        );
        var exits = this.getCurrentRoomExits();
        if (exits) msg += " " + exits;
      }
      this.print(msg, input.output_class);
      return;
    }

    // ok we think there's an exit
    var exit = this.world[exitID];
    this.game.log(
      "log",
      "high",
      "tryTravel.js > found exit: ",
      exit.id,
      "Travel"
    );

    // is there actually an exit object?
    if ("undefined" === typeof exit) {
      this.game.debug(`F1104 | tryTravel.js |no exit found matching ${exitID}`);
      msg += `That doesn't appear to be an exit. `;
      this.print(msg, input.output_class);
      return;
    }

    if ("undefined" !== typeof exit.aperture && "" !== exit.aperture) {
      var aperture = this.game.world[exit.aperture];
      var key_assets;
      var locked_msg = `${aperture.Articlename} is closed and locked. `;
      var sealed_msg = `${aperture.Articlename} is sealed. `;
      var closed_msg = `${aperture.Articlename} is closed. `;

      this.game.log(
        "log",
        "high",
        "tryTravel.js > found aperture " + aperture.id,
        "Travel"
      );

      //
      // LOCKED
      //
      if (aperture.isDOV("unlock") && aperture.is.locked) {
        key_assets = player.getIOVkeys("unlock", aperture);

        // if door is locked and player hasn't got key, pass
        if (!aperture.DOVallowWithNothing("unlock") && !key_assets.length) {
          this.game.debug(
            `F1105 | tryTravel.js | ${aperture.id}.is.locked and player hasn't got a key`
          );
          this.print(msg + locked_msg);
          return;
        }

        // never auto-unlock apertures the player hasn't already unlocked
        // 6/30/22 CHANGE: if aperture is locked and player didn't lock it
        if (
          !aperture.didDoVerbs({
            related_verbs: ["lock", "unlock", "pick", "open", "close"],
          })
        ) {
          this.game.debug(
            `F1106 | tryTravel.js | ${aperture.id}.is.locked and player did not lock it - no auto unlock on first unlock`
          );
          this.print(msg + locked_msg);
          return;
        }

        // check aperture settings for auto unlock
        if (!aperture.can.auto_unlock) {
          this.game.debug(
            `F1107 | tryTravel.js | ${aperture.id}.is.locked and .can.auto_unlock is false`
          );
          this.print(msg + locked_msg);
          return;
        }
      }

      //
      // SEALED
      //
      if (aperture.isDOV("unseal") && aperture.is.sealed) {
        key_assets = player.getIOVkeys("unseal", aperture);

        // has player got a key?
        if (!aperture.DOVallowWithNothing("unseal") && !key_assets.length) {
          this.game.debug(
            `F1108 | tryTravel.js | ${aperture.id}.is.sealed and player hasn't got a key`
          );
          this.print(msg + sealed_msg);
          return;
        }

        // never auto-unseal apertures the player hasn't already unsealed
        // 6/30/22 CHANGE: if aperture is sealed and player didn't seal it
        if (!aperture.didDoVerbs({ related_verbs: ["seal", "unseal"] })) {
          this.game.debug(
            `F1111 | tryTravel.js | ${aperture.id}.is.sealed and player did not seal it - no auto unseal on first unseal`
          );
          this.print(msg + sealed_msg);
          return;
        }

        // check aperture settings for auto unlock
        if (!aperture.can.auto_unseal) {
          this.game.debug(
            `F1109 | tryTravel.js | ${aperture.id}.is.sealed and .can.auto_unseal is false`
          );
          this.print(msg + sealed_msg);
          return;
        }
      }

      //
      // CLOSED
      //
      if (aperture.isDOV("open") && aperture.is.closed) {
        key_assets = player.getIOVkeys("open", aperture);

        // has player got a key?
        if (!aperture.DOVallowWithNothing("open") && !key_assets.length) {
          this.game.debug(
            `F1108 | tryTravel.js | ${aperture.id}.is.closed and player hasn't got a key`
          );
          this.print(msg + closed_msg);
          return;
        }

        // never auto-open apertures the player hasn't already opened
        // 6/30/22 CHANGE: if aperture is closed and player didn't close it
        if (!aperture.didDoVerbs({ related_verbs: ["open", "close"] })) {
          this.game.debug(
            `F1719 | tryTravel.js | ${aperture.id}.is.closed and player did not close it - no auto unseal on first unseal`
          );
          this.print(msg + closed_msg);
          return;
        }

        // check aperture settings for auto open
        if (!aperture.can.auto_open) {
          this.game.debug(
            `F1110 | tryTravel.js | ${aperture.id}.is.closed and .can.auto_open is false`
          );
          this.print(closed_msg);
          return;
        }
      }

      // check if aperture is hidden
      if (aperture.is.hidden) {
        this.game.debug(`F1112 | tryTravel.js | ${aperture.id}.is.hidden`);
        msg += `Hmm. You don't see any exit ${exit.direction}. `;
        this.print(msg, input.output_class);
        return;
      }
    } // if aperture

    // direction is valid but has no destination, try to print description
    if ("undefined" === typeof exit.destinationID && exit.descriptions.look) {
      this.game.log(
        "log",
        "high",
        "tryTravel.js > " +
          exit.direction +
          " has no destination. Printing description.",
        "Travel"
      );
      this.game.debug(
        `F1112 | tryTravel.js | ${exit.id}.destinationID is blank or undefined`
      );
      this.print(msg + A.getSAF.call(this, exit.descriptions.look));
      return;
    }

    // is player holding a rope that is tied to something in the room?
    // @TODO expand this to include other attachment types
    // such as things that are plugged into each other
    if (player.hasRopesThatBlockTravel()) {
      var ropes = player.getRopesThatBlockTravel();
      var rope = this.game.getAsset(ropes[0]);
      var object_rope_is_tied_to;
      for (var i = 0; i < rope.dov.tie?.with_params.connections.length; i++) {
        if (player.id !== rope.dov.tie?.with_params.connections[i]) {
          object_rope_is_tied_to = this.game.getAsset(
            rope.dov.tie?.with_params.connections[i]
          );
        }
      }
      this.game.debug(`F1113 | tryTravel.js |player.hasRopesThatBlockTravel`);
      msg = `$(We're) preventing from leaving by ${rope.articlename}`;
      if ("undefined" !== typeof object_rope_is_tied_to) {
        msg += ` tied to ${object_rope_is_tied_to.articlename}`;
      }
      msg += `. `;

      if (msg) this.game.print(msg, input.output_class);
      return null;
    }

    if (player.isNested() && false === nest_parent_object.is.rideable) {
      results = player.onUnnestThisFromThat(nest_parent_object);
      if ("undefined" !== typeof results) return results;
    }

    // doSuccess
    // direction is valid and destination exists
    if ("undefined" !== typeof exit.destinationID) {
      this.game.log(
        "log",
        "high",
        "tryTravel.js > Direction is valid and destination exists.",
        "Travel"
      );
      this.game.debug(
        `F1876 | tryTravel.js | doSuccess, travel to ${exit.destinationID}`
      );

      // TODO add logic for hidden exits
      exit.setUsed();
      var addPeriod = false;
      if ("undefined" !== typeof aperture) {
        if (aperture.is.locked && aperture.can.auto_unlock) {
          /*&& this.game.settings.can_auto_unlock_apertures*/
          msg += `$(We) unlock ${aperture.articlename}`;
          addPeriod = true;
          aperture.setLocked(false); //.locked = false;
          aperture.incrementDoVerbCount("unlock");
        }

        if (aperture.is.sealed && aperture.can.auto_unseal) {
          /*&& this.game.settings.can_auto_unseal_apertures*/
          msg += `$(We) unseal ${aperture.articlename}`;
          addPeriod = true;
          aperture.setSealed(false); //is.sealed = false;
          aperture.incrementDoVerbCount("unseal");
        }

        if (aperture.is.closed && aperture.can.auto_open) {
          /*&& this.game.settings.can_auto_open_apertures*/
          if (!msg) {
            msg += `$(We) open ${aperture.articlename}`;
          } else {
            msg += ", and then open it";
          }
          //+ ". ";
          addPeriod = true;
          aperture.setClosed(false);
          aperture.incrementDoVerbCount("open");
        }

        if (addPeriod) msg += ". ";
        aperture.setUsed();

        if ("undefined" !== typeof aperture.linked_asset) {
          this.game.world[aperture.linked_asset].setUsed();
        }
      }

      var newRoomID = exit.destinationID;
      var newRoomObj = this.world[newRoomID];
      for (var exitProp in newRoomObj.exits) {
        var newRoomExitID = newRoomObj.exits[exitProp];
        var newRoomExitObj = this.world[newRoomExitID];
        if (newRoomExitObj.destinationID === currentRoom.id) {
          newRoomExitObj.setUsed();
        }
      }

      if (player.isNested() && nest_parent_object.is.rideable) {
        //   results = newRoom.onMoveThatToThis( nest_parent_object, "in" ); // string
        //   if( false === results ) { return false; }
        //   else if ( null === results ) { return null; }
        msg += `$(We) ride ${nest_parent_object.articlename} `;
        if (this.game.dictionary.verbs[direction].is_spatial_direction) {
          //msg += "to ";
        } else if (this.game.dictionary.verbs[direction].is_compass_direction) {
          msg += `to the `;
        }
        msg += `${direction}. `;
      } else if (params.with) {
        msg += `$(We) push `;
        msg += this.game.getPrintableObjectList({
          objects: params.with,
          article: "definite",
        });
        msg += ` `;
        if (this.game.dictionary.verbs[direction].is_spatial_direction) {
          //msg += "to ";
        } else if (this.game.dictionary.verbs[direction].is_compass_direction) {
          msg += `to the `;
        }
        msg += `${direction}. `;
        if ("string" === typeof params.with) {
          params.with = [params.with];
        }
        for (var i = 0; i < params.with.length; i++) {
          var object = this.game.getAsset(params.with[i]);
          object.moveFrom(object.getPlaceAsset());
          object.moveTo("in", newRoomObj);
        }
      } else if (exit.descriptions && exit.descriptions.travel) {
        msg += A.getSAF.call(this.game, exit.descriptions.travel);
      } else {
        msg +=
          this.game.dictionary.verbs[input.input_verb] &&
          this.game.dictionary.verbs[input.input_verb].type.travel
            ? `$(We) ${input.input_verb} `
            : `$(We) ${player.getPostureVerb()} `;

        if (this.game.dictionary.verbs[direction].is_spatial_direction) {
          // relative directions include "port" and "starboard",
          // "left" and "right", etc
          //msg += "to ";
        }
        // else if(this.game.dictionary.verbs[direction].is_compass_direction)
        // {
        //   msg += "to the ";
        // }
        msg +=
          direction +
          " to " +
          (newRoomObj.use_definite_article_in_lists
            ? newRoomObj.definite_article + " "
            : "") +
          newRoomObj.name +
          ". ";
      }

      this.game.print(msg, input.output_class);
      this.setPlayerRoom(this.world[newRoomID], params);
      return true;
    }

    // final fallback
    this.game.debug(`F1114 | tryTravel.js |no ${direction} exit found`);
    msg += `There doesn't appear to be an exit ${direction}. `;
    if (
      this.settings.when_travel_fails_list_exits ||
      this.world[this.world._currentRoom].when_travel_fails_list_exits
    ) {
      this.game.debug(
        `F1119 | tryTravel.js | ${this.game_name}.settings.when_travel_fails_list_exits is true`
      );
      var exits = this.getCurrentRoomExits();
      if (exits) msg += " " + exits;
    }
    this.print(msg, input.output_class);
  };
})();