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

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

  /**
   * @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="ajs-player-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 subject'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 subject.
   * </p>
   * <p>
   * <code>goTo</code> will not open or unlock doors that the
   * subject has not already opened or unlocked. It will open
   * doors the subject has already opened, and it will unlock
   * doors the subject has already unlocked providing the subject
   * is carrying a key.
   * </p>
   * @ajsverbreactions doRemoveThisFromThat, doRemoveThatFromThis, doMoveThisToThat, doMoveThatToThis
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   * @TODO consider vehicles
   */
  A.Preverbs.goTo = {
    name: "goTo",
    prettyname: "go to",
    //synonyms: ["goto"],
    // verb_prep_noun: ["go to"],
    type: { locomotion: true, travel: true },

    subject_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 () {
      var input = this.game.getInput();
      var direct_object = input.getAsset(1);
      var destination = input.getNoun(1);
      var current_room = this.game.world._room;
      var msg = "";

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

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

    doSuccess: function () {
      var input = this.game.getInput();
      var subject = input.getSubject();
      var direct_object = input.getAsset(1);
      var destination = input.getNoun(1);
      var current_room = this.game.world._room;
      var msg = "";
      const print_room = this.game.settings.goto_prints_room_descriptions;
      const output_class = print_room ? "" : "concatenate_output";
      const exclude_unvisited =
        this.game.settings.goto_excludes_unvisited_locations;
      const exclude_locked = this.game.settings.goto_excludes_locked_doors;

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

      // if (destination === current_room) {
      //   this.game.debug(
      //     `D1296 | ${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 (!subject.hasVisitedRoom(room)) {
          // exclude rooms that subject hasn't visited yet
          this.game.log(
            "L1343",
            "log",
            "high",
            `[${this.name}.js] ${room.id} excluded from goTo because ${subject.id} hasn't been`,
            "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 subject hasn't used
          // if (!exit.is.used) {
          //   this.game.log(
          //     "L1344",
          //     "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 subject hasn't visited
          if (!subject.hasVisitedRoom(destination_room) && exclude_unvisited) {
            this.game.log(
              "L1345",
              "log",
              "high",
              `[${this.name}.js] ${subject.id}.has_been[${destination_room.id}] is unset`,
              "verbs"
            );
            continue;
          }

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

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

              // if door is locked and subject hasn't got key, pass
              if (
                !aperture.allowVerbWithNothing("unlock", "dov") &&
                !key_assets.length
              ) {
                this.game.log(
                  "L1346",
                  "log",
                  "high",
                  `[${this.name}.js] ${aperture.id}.is.locked and subject has no key`,
                  "verbs"
                );
                continue;
              }

              if (!aperture.canDoVerbAutomatically("unlock")) {
                continue;
              }
            }

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

              // has subject got a key?
              if (
                !aperture.allowVerbWithNothing("unseal", "dov") &&
                !key_assets.length
              ) {
                this.game.log(
                  "L1347",
                  "log",
                  "high",
                  `[${this.name}.js] ${aperture.id}.is.sealed and subject has no key`,
                  "verbs"
                );
                continue;
              }

              if (!aperture.canDoVerbAutomatically("unseal")) {
                continue;
              }
            }

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

              // has subject got a key?
              if (
                !aperture.allowVerbWithNothing("open", "dov") &&
                !key_assets.length
              ) {
                this.game.log(
                  "L1348",
                  "log",
                  "high",
                  `[${this.name}.js] ${aperture.id}.is.closed and subject has no key`,
                  "verbs"
                );
                continue;
              }

              if (!aperture.canDoVerbAutomatically("unlock")) {
                continue;
              }
            }

            if (aperture.is.hidden) {
              this.game.log(
                "L1349",
                "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("L1350", "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("L1351", "log", "low", ["graph:", graph], "verbs");

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

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

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

      if (
        "undefined" === typeof solutions[destination] ||
        0 === solutions[destination].length
      ) {
        this.game.debug(
          `D1297 | ${this.name}.js | 
            No path was found. 
            Blockers may include locked/sealed/closed doors which 
            subject has not opened or does not have a key for, 
            or destination unknown to / unvisited by subject. `
        );
        msg += "{We} {don't} know of a route between here and there";
        if (!destination?.is?.known && !subject.knowsAbout(destination)) {
          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(
                "L1353",
                "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)) {
                // INVOKE OPEN VERB
                this.game.parser.input_queue.push({
                  input: "open " + aperture.id,
                  printInput: false,
                  excludeRoomDescriptions: true,
                  output_class: output_class,
                  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: !print_room,
                output_class: output_class,
                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