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

(function () {
  /*global adventurejs A*/

  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(
      "L1078",
      "log",
      "high",
      `tryTravel.js > ${direction.direction || direction}`,
      "Travel"
    );
    if (direction.direction) direction = direction.direction;
    var input = this.game.getInput();
    var currentRoom = this.getCurrentRoom();
    var player = this.game.getPlayer();
    var nest_asset = player.getNestAsset();
    var nest_preposition = player.getNestPreposition();
    var nest_aspect = player.getNestAspect();
    var exit_id = currentRoom.exits[direction]; // may be no exit
    var exit, aperture;
    var msg = "";
    var results;

    input.did_tryTravel = true;

    // does this room have an exit in the specified direction?
    if (!exit_id) {
      this.game.debug(`D1115 | tryTravel.js |exit_id is undefined`);

      var global_object = this.game.world["global_" + direction];

      var global_description;
      var currentZone = this.game.world[currentRoom.zone];

      // try to get current room's scenery settings
      // description can be left undefined
      if (
        currentRoom.room_scenery[global_object.id]?.enabled &&
        currentRoom.room_scenery[global_object.id]?.description
      ) {
        // console.warn( "USE ROOM SCENERY OBJECT DESCRIPTION" );
        this.game.log(
          "L1491",
          "log",
          "high",
          `tryTravel.js > use ${currentRoom.id}.room_scenery.${global_object.id} for description`,
          "Travel"
        );
        global_description =
          currentRoom.room_scenery[global_object.id].description;
      }

      // otherwise try to get current room's zone scenery settings
      else if (
        currentZone?.zone_scenery[global_object.id]?.enabled &&
        currentZone?.zone_scenery[global_object.id]?.description
      ) {
        this.game.log(
          "L1491",
          "log",
          "high",
          `tryTravel.js > use ${currentZone.id}.zone_scenery.${global_object.id} for description`,
          "Travel"
        );
        // console.warn( "USE ZONE SCENERY OBJECT DESCRIPTION" );
        global_description =
          currentZone.zone_scenery[global_object.id].description;
      }

      // otherwise if room enables object,
      // try to get object's native (aka global) description
      else if (
        currentRoom.room_scenery[global_object.id]?.enabled &&
        global_object.description
      ) {
        this.game.log(
          "L1493",
          "log",
          "high",
          `tryTravel.js > use ${global_object.id}.description for description`,
          "Travel"
        );
        // console.warn( "USE GLOBAL OBJECT DESCRIPTION" );
        global_description = global_object.description;
      }

      if (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(
          `D1120 | tryTravel.js | 
          ${
            this.world[this.world._currentRoom].when_travel_fails_list_exits
              ? this.world._currentRoom + ".when_travel_fails_list_exits"
              : this.game_name + ".settings.when_travel_fails_list_exits"
          }`
        );
        var exits = this.getCurrentRoomExits();
        if (exits) msg += " " + exits;
      }
      this.print(msg, input.output_class);
      return null;
    }

    // ok we think there's an exit
    exit = this.game.getAsset(exit_id);
    aperture = this.game.getAsset(exit.aperture);
    this.game.log(
      "L1079",
      "log",
      "high",
      `tryTravel.js > found exit: ${exit.id} ${aperture ? "with aperture: " + aperture.id : ""} `,
      "Travel"
    );

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

    // is player constrained?
    if (player.is.constrained) {
      this.game.debug(`D1103 | 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;
    }

    // is player holding something like a rope or a railing?
    if (player.getVerbConnectionCount("hold", "to_dov") > 0) {
      // @TODO automatically let go
      this.game.debug(
        `D1133 | tryTravel.js | ${player.id}.is.connected_by.hold.to_dov contains ${player.is.connected_by.hold.to_dov}`
      );
      msg += `$(We'll) have to let go of ${this.game.getPrintableObjectList({
        objects: player.getVerbConnections("hold", "to_dov"),
      })}. `;
      if (msg) this.game.print(msg, input.output_class);
      return null;
    }

    // is player in/on a vehicle?
    if (
      nest_asset &&
      (nest_asset.hasClass("Vehicle") || nest_asset.isDOV("ride"))
    ) {
      var allowed = true;

      // does the exit allow vehicles?
      // does it allow THIS vehicle?
      if (
        (!exit.allow_vehicles &&
          !exit.allow_these_vehicles.includes(nest_asset.id)) ||
        exit.deny_these_vehicles.includes(nest_asset.id)
      ) {
        allowed = false;
      }

      if (!allowed) {
        this.game.debug(
          `D1323 | tryTravel.js | ${exit.id}.allow_vehicles is false or ${exit.id}.deny_these_vehicles contains ${nest_asset.id} `
        );
        msg += `$(We) can't ${input.input_verb} ${direction} ${exit.aperture ? this.game.getAsset(exit.aperture).articlename : ""} while ${nest_aspect.id} ${nest_asset.articlename}. `;
        if (msg) this.game.print(msg, input.output_class);
        return null;
      }
    }

    // is player nested in something?
    else if (nest_asset) {
      // by default, can't reach an exit while nested
      // but there are exceptions
      // @TODO add logic for vertical reachability of exit from nest

      var reachable = false;
      var reachable_assets;

      // @TODO this is no longer valid
      // is player nested behind/in/on/under something
      // that can reach this direction?
      if (nest_aspect) {
        reachable_assets = nest_aspect.things_player_can_reach_from_this_aspect;
        // @todo this no longer works - need to call canPlayerReach( direction )
        if (-1 < reachable_assets.indexOf(direction)) {
          reachable = true;
        }
      }

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

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

      if (!reachable) {
        this.game.debug(
          `D1134 | tryTravel.js | ${direction} is unreachable from ${nest_preposition} ${nest_asset.id}`
        );
        msg += `$(We) can't ${
          this.game.dictionary.getDirection(input.input_verb)
            ? "go"
            : input.input_verb
        } ${direction} from ${
          nest_preposition === "under" || nest_preposition === "behind"
            ? "$(our) position " + nest_preposition
            : ""
        } ${nest_asset.articlename}. `;
        if (msg) this.game.print(msg, input.output_class);
        return null;
      }
    }

    if (aperture) {
      var key_assets;
      var locked_msg = `${aperture.Articlename_is} closed and locked. `;
      var sealed_msg = `${aperture.Articlename_is} sealed shut. `;
      var closed_msg = `${aperture.Articlename_is} closed. `;

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

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

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

        if (!aperture.canDoVerbAutomatically("unlock")) {
          this.game.debug(
            `D1106 | tryTravel.js | ${aperture.id}.is.locked and either ${aperture.id}.dov.unlock.automatically is false or ${aperture.id}.dov.unlock.automatically_after_use is true and player has not previously unlocked ${aperture.id}`
          );
          this.print(msg + locked_msg);
          return false;
        }
      }

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

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

        if (!aperture.canDoVerbAutomatically("unseal")) {
          this.game.debug(
            `D1111 | tryTravel.js | ${aperture.id}.is.sealed and either ${aperture.id}.dov.unseal.automatically is false or ${aperture.id}.dov.unseal.automatically_after_use is true and player has not previously unsealed ${aperture.id}`
          );
          this.print(msg + sealed_msg);
          return false;
        }
      }

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

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

        if (!aperture.canDoVerbAutomatically("open")) {
          this.game.debug(
            `D1719 | tryTravel.js | ${aperture.id}.is.closed and either ${aperture.id}.dov.open.automatically is false or ${aperture.id}.dov.open.automatically_after_use is true and player has not previously opened ${aperture.id}`
          );
          this.print(msg + closed_msg);
          return false;
        }
      }

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

    // direction is valid but has no destination, try to print description
    if (
      "undefined" === typeof exit.destinationID &&
      (exit.descriptions.travel || exit.descriptions.look)
    ) {
      this.game.log(
        "L1081",
        "log",
        "high",
        "tryTravel.js > " +
          exit.direction +
          " has no destination. Printing description.",
        "Travel"
      );
      this.game.debug(
        `D1112 | tryTravel.js | ${exit.id}.destinationID is unset`
      );

      msg += A.getSAF.call(
        this,
        exit.descriptions.travel
          ? exit.descriptions.travel
          : exit.descriptions.look
      );

      if (
        this.settings.when_travel_fails_list_exits ||
        this.world[this.world._currentRoom].when_travel_fails_list_exits
      ) {
        this.game.debug(
          `D1124 | tryTravel.js | 
          ${
            this.world[this.world._currentRoom].when_travel_fails_list_exits
              ? this.world._currentRoom + ".when_travel_fails_list_exits"
              : this.game_name + ".settings.when_travel_fails_list_exits"
          }`
        );
        var exits = this.getCurrentRoomExits();
        if (exits) msg += " " + exits;
      }

      this.print(msg);
      return null;
    }

    // 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.is.connected_by.tie.to_iov.length; i++) {
        if (player.id !== rope.is.connected_by.tie.to_iov[i]) {
          object_rope_is_tied_to = this.game.getAsset(
            rope.is.connected_by.tie.to_iov[i]
          );
        }
      }
      this.game.debug(`D1113 | tryTravel.js |player.hasRopesThatBlockTravel`);
      msg = `$(We're) preventing from leaving by ${rope.articlename}`;
      if (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() && !nest_asset.isDOV("ride")) {
      results = player.onUnnestThisFromThat(nest_asset);
      if ("undefined" !== typeof results) return results;
    }

    // doSuccess
    // direction is valid and destination exists
    if (exit.destinationID) {
      this.game.log(
        "L1082",
        "log",
        "high",
        `tryTravel.js > doSuccess, travel to ${exit.destinationID}.`,
        "Travel"
      );

      // TODO add logic for hidden exits
      exit.setIs("used", true);
      var addPeriod = false;
      console.warn({ aperture });
      if (aperture) {
        if (aperture.is.locked && aperture.canDoVerbAutomatically("unlock")) {
          msg += `$(We) unlock ${aperture.articlename}`;
          addPeriod = true;
          aperture.setIs("locked", false);
          aperture.incrementDoVerbCount("unlock", "dov");
        }

        if (
          aperture.is.sealed &&
          aperture.can.canDoVerbAutomatically("unseal")
        ) {
          msg += `$(We) unseal ${aperture.articlename}`;
          addPeriod = true;
          aperture.setIs("sealed", false);
          aperture.incrementDoVerbCount("unseal", "dov");
        }

        if (aperture.is.closed && aperture.canDoVerbAutomatically("open")) {
          if (!msg) {
            msg += `$(We) open ${aperture.articlename}`;
          } else {
            msg += ", and then open it";
          }
          //+ ". ";
          addPeriod = true;
          aperture.setIs("closed", false);
          aperture.incrementDoVerbCount("open", "dov");
        }

        if (addPeriod) msg += ". ";
        aperture.setIs("used", true);

        if (aperture.linked_asset) {
          this.game.getAsset(aperture.linked_asset).setIs("used", true);
        }
      }

      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.setIs("used", true);
        }
      }

      if (player.isNested() && nest_asset.isDOV("ride")) {
        //   results = newRoom.onMoveThatToThis( nest_asset, "in" ); // string
        //   if( false === results ) { return false; }
        //   else if ( null === results ) { return null; }
        msg += `$(We) ride ${nest_asset.articlename} `;
        if (this.game.dictionary.verbs[direction].is_relative_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_relative_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_relative_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(`D1114 | 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(
        `D1119 | 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);
    return false;
  };
})();