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

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

  /**
   * @augments {adventurejs.Verb}
   * @class goTo
   * @ajsnode game.dictionary.verbs.goTo
   * @ajsconstruct MyGame.createVerb({ "name": "goTo", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading LocomotionVerbs
   * @summary Verb meaning go to location, or location of asset.
   * @tutorial Scripting_VerbSubscriptions
   * @tutorial Verbs_VerbAnatomy
   * @tutorial Verbs_VerbProcess
   * @tutorial Verbs_ModifyVerbs
   * @tutorial Verbs_WriteVerbs
   * @classdesc
   * <pre class="display border outline">
   * <span class="input">&gt; go to grand ballroom</span>
   * You go up to the mezzanine. You go south to the Grand Ballroom.
   *
   * <strong>Grand Ballroom</strong>
   * Balls everywhere. Literally. More of a ball pit, really.
   * </pre>
   * <p>
   * <strong>goTo</strong> is not called directly by the parser.
   * When a player enters <string>"go to"</string>, the verb
   * <code>go</code> is called, and forwards to <code>goTo</code>
   * if it's contextually suitable.
   * <code>goTo</code> is a special verb that will attempt
   * to map a path from the player's location to the specified
   * {@link adventurejs.Room|Room},
   * or the Room containing a specified
   * {@link adventurejs.Tangible|Tangible}
   * {@link adventurejs.Asset|Asset}.
   * The target asset must be known by player.
   * </p>
   * <p>
   * <code>goTo</code> will not open or unlock doors that the
   * player has not already opened or unlocked. It will open
   * doors the player has already opened, and it will unlock
   * doors the player has already unlocked providing the player
   * is carrying a key.
   * </p>
   * @ajsverbphases
   */
  A.Preverbs.goTo = {
    name: "goTo",
    prettyname: "go to",
    //synonyms: ["goto"],
    //verb_prep_noun: ["go to"],

    player_must_be: {
      not_constrained: true,
      not_nested_elsewhere: true,
    },

    /**
     * @ajsverbstructures
     * @memberof goTo
     */
    accepts_structures: ["verb noun"],

    /**
     * @memberof goTo
     * @ajsverbphrase
     * phrase1:
     * {
     *   not_global: true,
     *   accepts_noun: true,
     *   requires_noun: true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     tangible: true,
     *   },
     * },
     */
    phrase1: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        known: true,
        tangible: true,
      },
    },

    /**
     * @memberof goTo
     * @ajsverbparams
     * with_params: {},
     */
    with_params: {},

    msgNoObject: "Where did you want to go?",

    doTry: function () {
      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var direct_object = input.getAsset(1);
      var destination = input.getNoun(1);
      var current_room = this.game.world._currentRoom;
      var player = this.game.getPlayer();
      var msg = "";

      if (!(direct_object instanceof adventurejs.Room)) {
        destination = direct_object.getRoomId();
      }

      if (destination === current_room) {
        this.game.debug(
          `F1296 | ${this.name}.js | destination is current room `
        );
        msg += `Look around. `;
        this.game.print(msg);
        return false;
      }

      /**
       * We use layout to build a list of rooms and their connections.
       */
      var layout = {};
      for (var i = 0; i < this.game.room_lookup.length; i++) {
        var room = this.game.getAsset(this.game.room_lookup[i]);
        if (!room) {
          continue;
        }
        if (!room.player_has_visited) {
          // exclude rooms that player hasn't visited yet
          this.game.log(
            "log",
            "high",
            "${this.name}.js > " + room.id + ".player_has_visited is false",
            "verbs"
          );
          continue;
        }
        var nodes = [];
        var exits = room.exits;
        var keys = Object.keys(exits);
        for (var k = 0; k < keys.length; k++) {
          var exit = exits[keys[k]];
          exit = this.game.getAsset(exit);
          if (!exit) {
            continue;
          }
          var destination_room = this.game.getAsset(exit.destinationID);
          if (!destination_room) {
            continue;
          }
          var aperture = exit.aperture;

          // exclude exits that player hasn't used
          if (!exit.is.used) {
            this.game.log(
              "log",
              "high",
              `${this.name}.js > excluded ${exit.id}.is.used is false`,
              "verbs"
            );
            continue;
          }

          // exclude exits that have no destination
          if (!exit.destinationID) {
            continue;
          }

          // exclude destinations that player hasn't visited
          if (!destination_room.player_has_visited) {
            this.game.log(
              "log",
              "high",
              `${this.name}.js > ${destination_room.id}.player_has_visited is false`,
              "verbs"
            );
            continue;
          }

          // test exits for locked doors
          if (aperture) {
            var key_assets;
            aperture = this.game.getAsset(aperture);

            //
            // 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.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.is.locked and player has no key`,
                  "verbs"
                );
                continue;
              }

              // if door is locked and it wasn't locked by player, pass
              // 6/30/22 CHANGE: if aperture is locked and player didn't lock it
              if (
                !aperture.didDoVerbs({
                  related_verbs: ["lock", "unlock", "pick"],
                })
              ) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.is.locked and has never been unlocked by player`,
                  "verbs"
                );
                continue;
              }

              // if door was locked by player,
              // check this aperture's can.auto_unlock
              if (!aperture.can.auto_unlock) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.can.auto_unlock is false`,
                  "verbs"
                );
                continue;
              }

              // if door was locked by player,
              // check settings.can_auto_unlock_apertures
              if (
                true !== aperture.can.auto_unlock &&
                !this.game.settings.can_auto_unlock_apertures
              ) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${this.game.name}.settings.can_auto_unlock_apertures is false`,
                  "verbs"
                );
                continue;
              }
            }

            //
            // 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.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.is.sealed and player has no key`,
                  "verbs"
                );
                continue;
              }

              // 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.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.is.sealed and has never been unsealed by player`,
                  "verbs"
                );
                continue;
              }

              // check aperture settings for auto unlock
              if (!aperture.can.auto_unseal) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.can.auto_unseal is false`,
                  "verbs"
                );
                continue;
              }

              // check game settings for auto unseal
              if (
                !aperture.can.auto_unseal &&
                !this.game.settings.can_auto_unseal_apertures
              ) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${this.game.name}.settings.can_auto_unseal_apertures is false`,
                  "verbs"
                );
                continue;
              }
            }

            //
            // 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.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.is.closed and player has no key`,
                  "verbs"
                );
                continue;
              }

              // if door is closed and it wasn't closed by player, pass
              // 6/30/22 CHANGE: if aperture is closed and player didn't close it
              if (!aperture.didDoVerbs({ related_verbs: ["open", "close"] })) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.is.closed and was not closed by player`,
                  "verbs"
                );
                continue;
              }

              // check aperture settings for auto open
              if (!aperture.can.auto_open) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${aperture.id}.can.auto_open is false`,
                  "verbs"
                );
                continue;
              }

              // check game settings for auto open
              if (
                !aperture.can.auto_open &&
                !this.game.settings.can_auto_open_apertures
              ) {
                this.game.log(
                  "log",
                  "high",
                  `${this.name}.js > ${this.game.name}.settings.can.auto_open_apertures is false`,
                  "verbs"
                );
                continue;
              }
            }

            if (aperture.is.hidden) {
              this.game.log(
                "log",
                "high",
                `${this.name}.js > ${aperture.id}.is.hidden`,
                "verbs"
              );
              continue;
            }
          } // if aperture

          // good to go!
          nodes.push(exit.destinationID);
        }
        layout[room.id] = nodes;
      }
      this.game.log("log", "low", ["layout:", layout], "verbs");

      /**
       * We use graph to convert uni-directional to bi-directional.
       * needs to look like: where: { a: { b: cost of a->b }
       */
      var graph = {};
      for (var id in layout) {
        if (!graph[id]) {
          graph[id] = {};
        }
        layout[id].forEach(function (aid) {
          graph[id][aid] = 1;
          if (!graph[aid]) {
            graph[aid] = {};
          }
          graph[aid][id] = 1;
        });
      }

      this.game.log("log", "low", ["graph:", graph], "verbs");

      //choose start node
      var start = this.game.world._currentRoom;

      //get all solutions
      var solutions = A.dijkstra(graph, start);

      this.game.log(
        "log",
        "low",
        ["solutions[destination]:", solutions[destination]],
        "verbs"
      );

      if (
        "undefined" === typeof solutions[destination] ||
        0 === solutions[destination].length
      ) {
        this.game.debug(
          `F1297 | ${this.name}.js | No path was found. Blockers may include locked/sealed/closed doors which player has not opened or does not have a key for, or destination unknown to / unvisited by player. `
        );
        msg += "$(We) don't know of a route between here and there";
        if (!destination || !destination.is || !destination.is.known) {
          msg += ", or even if there is a there there";
        }
        msg += ".";
        this.handleFailure(msg);
        return false;
      } else {
        var len = solutions[destination].length;
        for (var i = 0; i < len; i++) {
          var thisRoom, nextRoom;

          if (0 === i) {
            thisRoom = start;
          } else {
            thisRoom = solutions[destination][i - 1];
          }
          nextRoom = solutions[destination][i];

          thisRoom = this.game.world[thisRoom];
          nextRoom = this.game.world[nextRoom];

          // cycle through the room's exits
          for (var direction in thisRoom.exits) {
            var exit = this.game.world[thisRoom.id + "_" + direction];
            var dest = exit.destinationID;

            // no destination? probably just a description
            if ("undefined" === typeof dest) {
              continue;
            }

            //
            if (nextRoom.id === dest) {
              this.game.log(
                "log",
                "low",
                ["nextRoom.id", nextRoom.id, "dest", dest],
                "verbs"
              );
              var output = "";

              var aperture = exit.aperture;
              if (aperture) aperture = this.game.getAsset(aperture);

              if (aperture && (aperture.is.locked || aperture.is.closed)) {
                // THIS METHOD MAY SIDESTEP AUTHOR SIDE EFFECTS
                //   output = "You ";
                //   if( aperture.is.locked ) {
                //     output += "unlock and open ";
                //     aperture.setLocked(false);//is.locked = false;
                //     aperture.setClosed(false);//is.closed = false;
                //   }
                //   else if( aperture.is.closed ) {
                //     output = "open ";
                //     aperture.setClosed(false);//is.closed = false;
                //   }
                //   output += " "
                //     + aperture.articlename
                //     + ". ";
                //this.game.print( output, "concatenate_output" );

                // THIS METHOD INVOKES OPEN VERB
                this.game.parser.input_queue.push({
                  input: "open " + aperture.id,
                  printInput: false,
                  excludeRoomDescriptions: true,
                  output_class: "concatenate_output",
                  linefeed: linefeed,
                });
              }

              /**
               * printing this output results in double output with tryTravel ie
               * You go west to the Eastern Room. $(We) move west.
               * You go west to the East Room. $(We) move west.
               * You go west to the Standing Room. $(We) move west.
               */
              output =
                "$(We) go " +
                direction +
                " to " +
                (nextRoom.use_definite_article_in_lists
                  ? nextRoom.definite_article + " "
                  : "") +
                nextRoom.name +
                ". ";

              // print a linefeed after the last item only
              var linefeed = i === len - 1 ? true : undefined;

              this.game.parser.input_queue.push({
                // print output: output,
                /* output was doubling "you move etc" messages
                  because tryTravel prints them too
                  leaving this here for now as an option 
                  for future dev */

                input: direction,
                printInput: false,
                excludeRoomDescriptions: true,
                output_class: "concatenate_output",
                linefeed: linefeed,
              });
              continue;
            }
          }
        }
      }

      //display solutions
      // console.log("From '"+start+"' to");
      // for(var s in solutions) {
      //   if(!solutions[s]) continue;
      //   if( s !== destination ) continue;
      //   console.log(" -> " + s + ": [" + solutions[s].join(", ") + "]   (dist:" + solutions[s].dist + ")");
      // }

      return true;
    },
  };
})(); // goTo