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

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

  /**
   * @augments {adventurejs.Verb}
   * @class go
   * @ajsnode game.dictionary.verbs.go
   * @ajsconstruct MyGame.createVerb({ "name": "go", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading LocomotionVerbs
   * @summary Verb meaning go [preposition] asset or travel [direction].
   * @ajssynonyms go
   * @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 in wardrobe</span>
   * You go in the wardrobe. Hey, it's cold in here! Better put
   * on a coat.
   * </pre>
   * <p>
   * <strong>Go</strong> can accept
   * directions and tangible assets.
   * For example, <strong>go east</strong> is equivalent
   * to entering just <strong>east</strong>;
   * <strong>go bed</strong> is equivalent to
   * <strong>get on bed</strong> and requires that the
   * {@link adventurejs.Tangible|Tangible}
   * {@link adventurejs.Asset|Asset} has an <strong>on</strong>
   * {@link adventurejs.Aspect|Aspect}.
   * See
   * <a href="/doc/Tangibles_Aspects.html">How to Use Aspects</a>
   * to learn more.
   * </p>
   * <p>
   * <strong>Go</strong> can be triggered via the verb
   * <strong>get</strong>, as in <strong>get down</strong>
   * or <strong>get on bed</strong> or
   * <strong>get out from under bed</strong>.
   * </p>
   * @ajsverbreactions
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.go = {
    name: "go",
    prettyname: "go",
    past_tense: "went",
    synonyms: ["go", "come"],
    type: { locomotion: true, travel: true },
    extensions: [
      "bounce",
      "crawl",
      "fly",
      "float",
      "hop",
      "hover",
      "jump",
      "ride",
      "run",
      "slither",
      "swim",
      "walk",
    ],

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

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

    /**
     * @memberof go
     * @ajsverbphrase
     * phrase1:
     * {
     *   accepts_noun: true,
     *   //requires_noun: true, // because we allow 'get down' and similar
     *   accepts_preposition: true,
     *   accepts_preposition_without_noun: true,
     *   noun_must_be: {
     *     not_global: true,
     *     tangible: true,
     *     known: true,
     *     present: true,
     *     visible: true,
     *     reachable: true,
     *   },
     * },
     */
    phrase1: {
      accepts_noun: true,
      accepts_preposition: true,
      accepts_preposition_without_noun: true,
      noun_must_be: {
        //not_global: true,
        tangible: true,
        known: true,
        //present: true,
        visible: true,
        reachable: true,
      },
    },

    /**
     * @memberof go
     * @ajsverbphrase
     * phrase2:
     * {
     *   accepts_noun:true,
     *   requires_noun:true,
     *   noun_must_be:
     *   {
     *     tangible: true,
     *     known: true,
     *     present: true,
     *     visible: true,
     *     reachable: true,
     *   },
     *   accepts_preposition: true,
     *   //requires_preposition: true,
     * },
     */
    phrase2: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        tangible: true,
        known: true,
        present: true,
        visible: true,
        reachable: true,
      },
      accepts_preposition: true,
      //requires_preposition: true,
    },

    /**
     * @memberof go
     * @ajsverbphrase
     * phrase3:
     * {
     *   accepts_noun:true,
     *   requires_noun:true,
     *   noun_must_be:
     *   {
     *     tangible: true,
     *     known: true,
     *     present: true,
     *     visible: true,
     *     reachable: true,
     *   },
     *   accepts_preposition: true,
     *   requires_preposition: true,
     * },
     */
    phrase3: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        tangible: true,
        known: true,
        present: true,
        visible: true,
        reachable: true,
      },
      accepts_preposition: true,
      requires_preposition: true,
    },

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

    in_can_mean_on: true,

    // the ways GO can be used
    // no object, no preposition - go
    // no object, preposition - go off
    // object, no preposition - go east, go bed
    // object, preposition - go on bed, go on east
    doTry: function () {
      var input = this.game.getInput();
      var input_verb = input.input_verb;
      var Input_verb = A.propercase(input_verb);
      var dictionary_verb = input.getDictionaryVerb();
      var player = this.game.getPlayer();
      var nest_preposition = player.getNestPreposition();
      var nest_asset = player.getNestAsset();
      var direct_object = input.getAsset(1);
      var direct_preposition = input.getPreposition(1);
      var prep_exit = this.game.getExitFromDirection(direct_preposition);
      var indirect_object = input.getAsset(2);
      var indirect_preposition = input.getPreposition(2);
      var indirect_object2 = input.getAsset(3);
      var indirect_preposition2 = input.getPreposition(3);
      var msg = "";
      var results;
      var outfrombehind = direct_preposition === "outfrombehind";
      var outfromunder = direct_preposition === "outfromunder";
      if (outfrombehind || outfromunder) {
        input.verb_params[
          outfromunder ? "outfromunder" : "outfrombehind"
        ] = true;
        input.setPreposition(1, "out");
        direct_preposition = "out";
      }
      input.verb_params.found = {
        up: false,
        down: false,
        off: false,
        out: false,
        in: false,
        fromto: false,
        with: false,
        goto: false,
      };

      if (direct_object?.is.global && direct_object.id !== "global_floor") {
        // @TODO floor handling
        this.game.debug(
          `F1725 | ${this.name}.js | ${direct_object.id}.is.global `
        );
        msg += `$(We) can't go to ${direct_object.articlename}. `;
        this.handleFailure(msg);
        return null;
      }

      // check for extensions
      if (dictionary_verb.extends.go) {
        // @TODO and do what?
        // We got here by a verb other than go,
        // such as crawl, slither, fly, etc.
        // That verb went through qualifyParsedVerb on its
        // way here so we ought to be able to act on it.
      }

      if (indirect_object && indirect_object instanceof adventurejs.Floor) {
        // if (
        //   indirect_preposition &&
        //   ["to", "on"].indexOf(indirect_preposition) > -1
        // ) {
        //   if (direct_preposition) {
        //     direct_preposition = "off";
        //     input.setPreposition(1, "off");
        //     input.deletePhrase(2);
        //   }
        // }
      }

      if (direct_object && direct_object instanceof adventurejs.Floor) {
        // "go floor" means "get off", delete phrase1
        //input.deletePhrase(1);
      }

      if (input.hasStructure("verb")) {
        // go doesn't handle "verb"
        // though other locomotion verbs do
        if (dictionary_verb.accepts_structures.indexOf("verb") === -1) {
          this.game.debug(
            `F1182 | ${dictionary_verb.name}.js | no direct_object, soft prompt noun1`
          );
          msg += `Where did $(we) want to ${input.input}? `;
          input.setSoftPrompt({
            noun1: true,
            input_verb: input.input_verb,
            verb: input.input_verb,
            structure: "verb noun",
          });
          this.handleFailure(msg);
          return null;
        } // else return true;
      }

      if (input.hasStructure("verb preposition noun")) {
        if (input.input_verb === "go" && direct_preposition === "to") {
          if (direct_object instanceof adventurejs.Room)
            input.verb_params.found.goto = true;
          if (!(direct_object instanceof adventurejs.Room)) {
            let destination = direct_object.getRoomId();
            let current_room = this.game.getCurrentRoom();
            if (destination !== current_room.id)
              input.verb_params.found.goto = true;
          }
          if (input.verb_params.found.goto) {
            // is player nested?
            if (nest_asset && !nest_asset.isIn(player)) {
              this.game.debug(
                `F1210 | ${
                  this.name
                }.js | player is nested ${player.getNestPreposition()} ${
                  nest_asset.id
                }`
              );
              msg += `$(We'll) have to get ${player.getPrettyUnnestPreposition()} ${
                nest_asset.articlename
              } first. `;
              this.handleFailure(msg);
              return null;
            }
            if (this.game.dictionary.verbs.goTo) {
              this.game.debug(
                `F1295 | ${this.name}.js | destination is not in current room, doVerb goTo `
              );
              this.game.dictionary.doVerb("goTo");
              return null;
            } else {
              this.game.debug(
                `F1092 | ${this.name}.js | destination is not in current room and verb goTo is not enabled `
              );
              msg += `$(We) can't get to ${direct_object.articlename} from here. `;
              this.handleFailure(msg);
              return null;
            }
          }
        }
      }
      if (
        direct_object &&
        !this.game.parser.selectPresent([direct_object]).length
      ) {
        this.game.debug(
          `F1201 | ${this.name}.js | ${direct_object.id}.is.global `
        );
        msg += `There doesn't appear to be any ${
          input.replacements[direct_object.id]
            ? input.replacements[direct_object.id].source
            : direct_object.name
        } present. `;
        this.handleFailure(msg);
        return null;
      }

      input.verb_params.found.down =
        (direct_object && direct_object.direction === "down") ||
        direct_preposition === "off" ||
        direct_preposition === "down" ||
        (direct_preposition === "from" && !indirect_preposition);

      input.verb_params.found.up =
        (direct_object && direct_object.direction === "up") ||
        (direct_object && !direct_preposition) ||
        direct_preposition === "up" ||
        direct_preposition === "on" ||
        direct_preposition === "over";

      // sentence structure: verb preposition ----------
      // ex: go up, go down, go in, go out, go off
      if (input.hasStructure("verb preposition")) {
        if (!nest_asset && prep_exit) {
          // this should catch go up, go down, go in, go out
          // while player is not nested, and set a direction
          this.game.debug(
            `F1121 | ${this.name}.js | received preposition ${direct_preposition}, convert to direction`
          );
          this.game.print(msg);
          input.setAsset(1, prep_exit);
          input.setPreposition(1, "");
          input.setStructure("verb noun");
          direct_object = input.getAsset(1);
          direct_preposition = "";
        }
      }

      if (input.hasStructure("verb preposition")) {
        // the only prepositions that can be figured from context are
        // go in, go out, go up, go down, go off

        switch (direct_preposition) {
          case "up":
            if (nest_asset) {
              if (
                nest_asset.is.climbable &&
                player.getY() < nest_asset.getYTop()
              ) {
                // let player go higher
                direct_object = nest_asset;
                input.setAsset(1, nest_asset);
                input.setStructure("verb preposition noun");
              }
              if (player.getY() >= nest_asset.getYTop()) {
                // @TODO reach up exit from nest
                // ex: player is standing on ladder from which they can reach up exit
                if (prep_exit) {
                  this.game.debug(
                    `F1152 | ${this.name}.js | player is at top of ${nest_asset.id}, tryTravel ${prep_exit.direction}`
                  );
                  this.game.tryTravel(prep_exit.direction);
                  return null;
                }
                this.game.debug(
                  `F1047 | ${this.name}.js | player is at top of ${nest_asset.id} `
                );
                msg += `$(We) can't get any higher on ${nest_asset.articlename}. `;
                this.handleFailure(msg);
                return null;
              }
            }
            break;
          case "down": //----------
            if (nest_asset) {
              if (
                player.getY() - nest_asset.getY() <=
                this.game.settings.reach_height
              ) {
                input.setPreposition(1, "off");
                direct_preposition = "off";
              }
              // let player go lower
              direct_object = nest_asset;
              input.setAsset(1, nest_asset);
              input.setStructure("verb preposition noun");
            }
            break;
          case "in": //----------
            input.setAsset(1, prep_exit);
            input.setPreposition(1, "");
            input.setStructure("verb noun");
            direct_object = input.getAsset(1);
            direct_preposition = "";
            break;
          case "out": //----------
            // we'll be generous in inferring what "out" means
            if (
              nest_asset &&
              ["on", "in", "under", "behind"].indexOf(nest_preposition) > -1
            ) {
              // player is nested and input "get out", infer "get out of nest"
              this.game.debug(
                `F1129 | ${this.name}.js | infer 'get out of ${nest_asset.id}'`
              );
              direct_object = nest_asset;
              input.setAsset(1, nest_asset);
              input.setStructure("verb preposition noun");
            } else if (nest_asset) {
              // this is unlikely to happen
              this.game.debug(
                `F1130 | ${this.name}.js | player nested  ${nest_preposition} ${nest_asset.id} `
              );
              msg += `$(We) can't get out of ${nest_asset.articlename}. `;
              this.handleFailure(msg);
              return null;
            }
            break;
          case "off": //----------
            if (nest_asset) {
              this.game.debug(
                `F1205 | ${this.name}.js | infer 'get off of ${nest_asset.id}'`
              );
              direct_object = nest_asset;
              input.setAsset(1, nest_asset);
              input.setStructure("verb preposition noun");
            } else {
              // @TODO what if player is riding a bus?
              this.game.debug(`F1046 | ${this.name}.js | nothing to get off `);
              msg += `$(We're) not on anything to ${input.input_verb} off. `;
              this.handleFailure(msg);
              return null;
            }
            break;
        }
      } // go in, go out, go up, go down, go off

      // still no direct object? prompt player
      if (input.hasStructure("verb preposition")) {
        if (!direct_object) {
          this.game.debug(
            `F1001 | ${this.name}.js | no direct_object, soft prompt noun1`
          );
          msg += `Where did $(we) want to ${input.input}? `;
          input.setSoftPrompt({
            noun1: true,
            structure: "verb preposition noun",
          });
          this.handleFailure(msg);
          return null;
        }
      }

      // sentence structure: verb noun ----------

      // ex: climb tree, climb east
      // try to get a preposition
      if (input.hasStructure("verb noun")) {
        // direct_object is a direction
        if (direct_object.direction) {
          // for example, "go down"

          // direct_object is direction - down and player is on climbable
          if ("down" === direct_object.direction) {
            if (nest_asset) {
              // is it climbable?
              if (
                nest_asset.is.climbable &&
                player.position.y > nest_asset.position.y
              ) {
                // player may be able to go lower on nest parent
                // or get off it
                this.game.debug(
                  `F1124 | ${this.name}.js | noun is direction/down and nest is climbable, doVerb climb`
                );
                input.setAsset(1, nest_asset);
                input.setPreposition(1, "down");
                input.setVerb("climb");
                this.game.dictionary.doVerb("climb");
                return null;
              }

              // otherwise treat it as ordinary get down
              else {
                return true; // just do it
              }
            } // down & nest_asset // tryTravel down
            else {
              this.game.debug(
                `F1861 | ${this.name}.js | ${direct_object.id}.direction is ${direct_object.direction}, tryTravel ${direct_object.direction}`
              );
              this.game.tryTravel(direct_object.direction);
              return null;
            }
          } // down

          // direct_object is direction - up and player is on climbable
          else if (
            "up" === direct_object.direction &&
            nest_asset &&
            nest_asset.is.climbable
          ) {
            this.game.debug(
              `F1125 | ${this.name}.js | noun is direction/up and nest is climbable, doVerb climb`
            );
            // player may be able to go higher on nest parent
            //input.parsedNoun1 = new adventurejs.ParsedNoun( nest_asset );
            input.setAsset(1, nest_asset);
            this.game.dictionary.doVerb("climb");
            return null;
          }

          // direct_object is any other direction - tryTravel
          else {
            this.game.debug(
              `F1131 | ${this.name}.js | ${direct_object.id}.direction is ${direct_object.direction}, tryTravel ${direct_object.direction}`
            );
            this.game.tryTravel(direct_object.direction);
            return null;
          }
        } // direct_object is direction

        if (
          !nest_asset &&
          direct_object.is.climbable &&
          0 < direct_object.dimensions.height
        ) {
          this.game.debug(
            `F1208 | ${this.name}.js | ${direct_object.id}.is.climbable, doVerb climb`
          );
          this.game.dictionary.doVerb("climb");
          return null;
        }

        if (!nest_asset && direct_object.default_aspect) {
          direct_preposition = direct_object.default_aspect;
          input.setPreposition(1, direct_preposition);
          input.setStructure("verb preposition noun");
        }

        // for example, "go bed"
        if (
          (nest_asset && nest_asset.id === direct_object.id) ||
          (!nest_asset && direct_object instanceof adventurejs.Floor)
        ) {
          // player already nested in this asset
          this.game.debug(
            `F1209 | ${this.name}.js | player nested  ${nest_preposition} ${direct_object.id} `
          );
          msg += `$(We're) already ${
            nest_preposition ? nest_preposition : direct_object.default_aspect
          } ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // for example, "go bed"
        if (nest_asset && nest_asset.id !== direct_object.id) {
          input.swapPhrases(1, 2);
          input.setPreposition(1, "from");
          input.setPreposition(2, "to");
          input.setAsset(1, nest_asset);
          direct_object = input.getAsset(1);
          direct_preposition = input.getPreposition(1);
          indirect_object = input.getAsset(2);
          indirect_preposition = input.getPreposition(2);
          input.verb_params.found.fromto = true;
          input.setStructure("verb preposition noun preposition noun");
        }
      } // verb noun

      // sentence structure: verb preposition noun ----------

      // ex: get on bed, go up tree
      // verify preposition and noun
      if (input.hasStructure("verb preposition noun")) {
        if (direct_object.direction) {
          // for example, "go down"
          if (-1 !== ["through", "out", "in"].indexOf(direct_preposition)) {
            // we'll accept these prepositions for exits
            this.game.debug(
              `F1230 | ${this.name}.js | ${direct_object.id}.direction is ${direct_object.direction}, tryTravel ${direct_object.direction}`
            );
            this.game.tryTravel(direct_object.direction);
            return null;
          }

          // otherwise, direct_preposition is not understood
          this.game.debug(
            `F1375 | ${this.name}.js | ${direct_preposition} ${direct_object.id} not understood `
          );
          msg += `$(We're) not sure how to <em class='unparsed'>${input.input}</em>. `;
          this.handleFailure(msg);
          return null;
        }

        if (direct_preposition === "to") {
          if (!nest_asset) {
            // try to find an aspect on the direct_object
            if (
              direct_object.aspects[direct_object.default_aspect] &&
              direct_object.aspects[direct_object.default_aspect].player.can
                .enter
            ) {
              direct_preposition = direct_object.default_aspect;
              input.setPreposition(1, direct_preposition);
            } else {
              this.game.debug(
                `F1726 | ${this.name}.js | ${direct_object.id} is present in room`
              );
              msg += `$(We) can't get any closer to  ${direct_object.articlename}. `;
              this.handleFailure(msg);
              return null;
            }
          }

          // is player trying to go to the thing they're already in?
          if (nest_asset && nest_asset.id === direct_object.id) {
            this.game.debug(
              `F1728 | ${
                this.name
              }.js | player is nested ${player.getNestPreposition()} ${
                direct_object.id
              }`
            );
            msg += `$(We're) already ${player.getNestPreposition()} ${
              direct_object.articlename
            }. `;
            this.handleFailure(msg);
            return null;
          }

          // upgrade to "go from a to b"
          if (nest_asset) {
            input.swapPhrases(1, 2);
            input.setPreposition(1, "from");
            input.setAsset(1, nest_asset);
            direct_object = input.getAsset(1);
            direct_preposition = input.getPreposition(1);
            indirect_object = input.getAsset(2);
            indirect_preposition = input.getPreposition(2);
            input.verb_params.found.fromto = true;
            input.setStructure("verb preposition noun preposition noun");
          }
        }

        // player has input "get off [asset]"
        // or we've resolved to off from another preposition

        if ("off" === direct_preposition) {
          if (!nest_asset) {
            this.game.debug(
              `F1045 | ${this.name}.js | player not on ${direct_object.id} `
            );
            msg += `$(We're) not on ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }
          if (nest_asset && "on" !== nest_preposition) {
            this.game.debug(
              `F1511 | ${this.name}.js | ${nest_preposition} ${nest_asset.id} `
            );
            msg += `$(We're) ${nest_preposition} ${nest_asset.articlename}, not on it. `;
            this.handleFailure(msg);
            return null;
          }

          if (nest_asset.id === direct_object.id) {
            // player can't get off, which can happen with platforms
            if (direct_object.is.unleavable) {
              this.game.debug(
                `F1002 | ${this.name}.js | ${direct_object.id}.is.unleavable is true`
              );
              msg += `$(We) can't exit ${direct_object.articlename} that way. `;
              this.handleFailure(msg);
              return null;
            }

            if (
              direct_object.is.climbable &&
              0 < direct_object.dimensions.height
            ) {
              this.game.debug(
                `F1126 | ${this.name}.js | ${direct_object.id}.is.climbable, doVerb climb`
              );
              input.input_verb = "climb";
              input.setPreposition(1, "down");
              input.setStructure("verb preposition");
              this.game.dictionary.doVerb("climb");
              return null;
            }
          }
        } // direct_preposition is off

        if ("out" === direct_preposition) {
          // did player input "get out from under" or "get out from behind"
          // but is not actually under or behind?
          if (
            (outfrombehind && "behind" !== nest_preposition) ||
            (outfromunder && "under" !== nest_preposition)
          ) {
            var p = outfromunder ? "under" : "behind";
            this.game.debug(
              `F1000 | ${this.name}.js | player is ${nest_preposition} ${nest_asset.id} not ${p} ${direct_object.id}`
            );
            msg += `$(We're) not ${p} ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }
          input.verb_params.found.out = true;
        }

        if ("off" === direct_preposition || "out" === direct_preposition) {
          if (nest_asset.id !== direct_object.id) {
            // player not in/on direct object
            var p = "off" === direct_preposition ? "on" : "in";
            this.game.debug(
              `F1140 | ${this.name}.js | player is ${nest_preposition} ${nest_asset.id} not ${p} ${direct_object.id}`
            );
            msg += `$(We're) not ${p} ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }

          // what if player is sitting in a chair in, eq, a submarine?
          if (direct_object.id === this.game.world._currentRoom) {
            if (direct_object.player.can.exit && nest_asset) {
              this.game.debug(`F1003 | ${this.name}.js | player.isNested`);
              msg += `$(We) can't do that from $(our) position ${player.getPostureGerund()} ${nest_preposition} ${
                nest_asset.articlename
              }. `;
              this.handleFailure(msg);
              return null;
            }

            // player is on floor in, eg, a submarine?
            if (
              direct_object.player_can_exit &&
              /*&& !nest_asset*/
              player.isOnFloor()
            ) {
              this.game.debug(`F1004 | ${this.name}.js | player.isOnFloor `);
              msg += `$(We) can't do that from $(our) position ${player.getPostureGerund()} on the floor. `;
              this.handleFailure(msg);
              return null;
            }

            // what if player is in, eg, a submarine?
            // redirect to exit verb
            if (direct_object.player_can_exit) {
              this.game.debug(
                `F1127 | ${this.name}.js | ${direct_object.id}.player_can_exit, doVerb exit`
              );
              this.game.dictionary.doVerb("exit");
              return null;
            }
          }
        } // direct_preposition is off or out

        // player is sitting/lying on something else
        // @todo need to consider reachability
        // as is this prevents all movement between nests
        if (nest_asset && nest_asset.id !== direct_object.id) {
          // upgrade to "go from a to b"
          input.swapPhrases(1, 2);
          input.setAsset(1, nest_asset);
          input.setPreposition(1, "from");
          input.setPreposition(2, "to");
          direct_object = input.getAsset(1);
          direct_preposition = input.getPreposition(1);
          indirect_object = input.getAsset(2);
          indirect_preposition = input.getPreposition(2);
          input.verb_params.found.fromto = true;
          input.setStructure("verb preposition noun preposition noun");

          // this.game.debug(`F1005 | ${this.name}.js | player.isNested ${nest_preposition} ${nest_asset.id} <br/> @todo add reachability check`);
          // msg += `$(We) can't do that from $(our) position ${player.getPostureGerund()} ${nest_preposition} ${
          //   nest_asset.articlename
          // }. `;
          // this.handleFailure(msg);
          // return null;
        }
      }

      // ask this again because we may have changed structure
      if (input.hasStructure("verb preposition noun")) {
        // not off or out
        if (
          "down" === direct_preposition &&
          direct_object.is.climbable &&
          0 < direct_object.dimensions.height
        ) {
          this.game.debug(
            `F1208 | ${this.name}.js | ${direct_object.id}.is.climbable, doVerb climb`
          );
          this.game.dictionary.doVerb("climb");
          return null;
        }

        if (
          ("on" === direct_preposition || "up" === direct_preposition) &&
          direct_object.is.climbable &&
          0 < direct_object.dimensions.height
        ) {
          this.game.debug(
            `F1128 | ${this.name}.js | ${direct_object.id}.is.climbable, doVerb climb`
          );
          if ("on" === direct_preposition) input.setPreposition(1, "up");
          this.game.dictionary.doVerb("climb");
          return null;
        } else if ("up" === direct_preposition) {
          input.setPreposition(1, "on");
          direct_preposition = "on";
        }

        // player is on floor
        if (!player.isNested() && player.isOnFloor()) {
          this.game.debug(`F1006 | ${this.name}.js | player.isOnFloor `);
          msg += `$(We) can't do that from $(our) position ${player.getPostureGerund()} on the floor. `;
          this.handleFailure(msg);
          return null;
        }

        // player is already nested within target
        if (
          (direct_object.id === nest_asset.id &&
            direct_preposition === nest_preposition) ||
          (!nest_asset && direct_object instanceof adventurejs.Floor)
        ) {
          this.game.debug(
            `F1007 | ${this.name}.js | player.isNested ${nest_preposition} ${nest_asset.id}`
          );
          msg += `$(We're) already ${
            nest_preposition ? nest_preposition : direct_object.default_aspect
          } ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        if ("from" === direct_preposition) {
          direct_preposition = "off";
          input.setPreposition(1, "off");
        }

        if (["off", "out"].indexOf(direct_preposition) === -1) {
          // player is nested some other way with target
          // and needs to unnest first
          if (
            direct_object.id === nest_asset.id &&
            direct_preposition !== nest_preposition
          ) {
            this.game.debug(
              `F1008 | ${this.name}.js | player.isNested ${nest_preposition} ${nest_asset.id} not ${direct_preposition}`
            );
            msg += `$(We) can't do that from $(our) position ${player.getPostureGerund()} ${nest_preposition} ${
              nest_asset.articlename
            }. `;
            this.handleFailure(msg);
            return null;
          }

          // player can't nest in/on object at all
          if (direct_preposition) {
            if (
              "in" === direct_preposition &&
              direct_object.quirks.in_means_on
            ) {
              direct_preposition = "on";
              input.setPreposition(1, "on");
            }

            if (
              !direct_object.hasAspectAt(direct_preposition) ||
              !direct_object.aspects[direct_preposition].player.can.enter
            ) {
              this.game.debug(
                `F1009 | ${this.name}.js | ${direct_object.id}.aspects.${direct_preposition} is undefined or .player.can.enter is false`
              );
              msg += `$(We) can't go ${direct_preposition} ${direct_object.articlename}. `;
              this.handleFailure(msg);
              return null;
            }
          }
        } // off or out, else
      } // verb preposition noun

      // sentence structure: verb noun preposition noun ----------
      // ex: go east on bridge, go east on bike
      // go up with piton, go up with jetpack

      if (input.hasStructure("verb noun preposition noun")) {
        // accepts "go east on ob", "go east in ob", "go east with ob"
        if (["with", "in", "on"].indexOf(indirect_preposition) === -1) {
          this.game.debug(`F1867 | ${this.name}.js | irregular phrase `);
          msg += this.game.parser.getUnparsedMessage(input.input);
          this.handleFailure(msg);
          return null;
        }

        if (direct_object.direction && indirect_object.is.rideable) {
          // player input something like "go east on bike"
          // is player on the bike?
          // @TODO what about something like "go east on bridge"
          // where bridge is a thing you can travel on?
          if (!nest_asset || nest_asset.id !== indirect_object.id) {
            this.game.debug(
              `F1042 | ${this.name}.js | player is not on ${indirect_object.name} `
            );
            msg += `$(We're) not on ${indirect_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }
          // else ok
        }
        // are there any other valid uses for verb noun preposition noun?
        // ex: go east under bridge?
        else {
          this.game.debug(`F1871 | ${this.name}.js | irregular phrase `);
          msg += this.game.parser.getUnparsedMessage(input.input);
          this.handleFailure(msg);
          return null;
        }
      } // verb noun preposition noun

      // sentence structure: verb preposition noun preposition noun ----------
      // ex: go from bed to bath
      // go up cliff with piton

      if (input.hasStructure("verb preposition noun preposition noun")) {
        // 'go from a to b' and 'go up a with b' are the only
        // phrases we accept in this form

        // with ----------

        if ("with" === indirect_preposition) {
          input.verb_params.with = true;

          results = this.checkWith(
            direct_preposition,
            direct_object,
            indirect_object
          );
          if (results.failure) {
            this.handleFailure(results.msg);
            return null;
          }
          if (results.return) return true;
          input.verb_params.found.with = true;

          // is player nested?
          if (nest_asset && nest_asset.id !== direct_object.id) {
            input.swapPhrases(2, 3);
            input.swapPhrases(1, 2);
            input.setAsset(1, nest_asset);
            input.setPreposition(1, "from");
            direct_preposition = "from";
            direct_object = input.getAsset(1);
            indirect_preposition = input.getPreposition(2);
            indirect_object = input.getAsset(2);
            indirect_preposition2 = input.getPreposition(3);
            indirect_object2 = input.getAsset(3);
            input.setStructure(
              "verb preposition noun preposition noun preposition noun"
            );
            input.verb_params.found.fromto = true;
          }
        } // with

        // to from ----------

        if (direct_preposition === "to" && indirect_preposition === "from") {
          // reverse them
          var phrase2 = Object.assign({}, input.getPhrase(2));
          var phrase1 = Object.assign({}, input.getPhrase(1));

          input.setPhrase(1, phrase2);
          input.setPhrase(2, phrase1);

          direct_object = input.getAsset(1);
          direct_preposition = input.getPreposition(1);

          indirect_object = input.getAsset(2);
          indirect_preposition = input.getPreposition(2);
        }

        // from to ----------

        if (!input.verb_params.found.fromto)
          input.verb_params.found.fromto =
            direct_preposition === "from" && indirect_preposition === "to";

        if (input.verb_params.found.fromto) {
          results = this.canPlayerGoFromThisToThat(
            direct_object,
            indirect_object
          );
          if (results.failure) {
            this.handleFailure(results.msg);
            return null;
          }
          if (results.return) return true;
          input.verb_params.found.fromto = true;
        }
      } // verb preposition noun preposition noun

      // sentence structure: verb preposition noun preposition noun preposition noun
      // ex: go from ramp to ledge with grapple (this is a stretch)

      if (
        input.hasStructure(
          "verb preposition noun preposition noun preposition noun"
        )
      ) {
        if (indirect_preposition2 === "with" && !input.verb_params.found.with) {
          results = this.checkWith(
            indirect_preposition,
            indirect_object,
            indirect_object2
          );
          if (results.failure) {
            this.handleFailure(results.msg);
            return null;
          }
          if (results.return) return true;
          input.verb_params.found.with = true;
        }

        if (
          direct_preposition === "from" &&
          indirect_preposition === "to" &&
          !input.verb_params.found.fromto
        ) {
          if (nest_asset && nest_asset.id !== direct_object.id) {
            this.game.debug(
              `F1874 | ${this.name}.js | player is ${nest_preposition} ${nest_asset.id} not on ${direct_object.id}`
            );
            msg += `$(We're) not on ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }
        }
      }

      // save our findings for doSuccess so we don't have to repeat this logic

      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var input_verb = input.input_verb;
      var player = this.game.getPlayer();
      var nest_asset = player.getNestAsset();
      var posture, preposition;
      var direct_object = input.getAsset(1);
      var direct_preposition = input.getPreposition(1);
      var indirect_object = input.getAsset(2);
      var indirect_preposition = input.getPreposition(2);
      var indirect_object2 = input.getAsset(3);
      var indirect_preposition2 = input.getPreposition(3);
      var msg = "";
      var results;
      var newY = 0;
      var newPoint = { y: 0 };

      this.game.debug(
        `F1189 | ${
          this.name
        }.js | doSuccess sentence structure ${input.getStructure()}`
      );

      // ---------- sentence structure: verb

      if (input.hasStructure("verb")) {
        this.game.debug(
          `F1875 | ${this.name}.js | ${input.input_verb} given with no object or preposition`
        );
        msg += `$(We) ${input.input_verb} about for a bit. `;
      }

      // ---------- sentence structure: verb preposition noun

      if (input.hasStructure("verb preposition noun")) {
        // If direct_object is an exit/aperture, we already shunted to "enter".

        // out ----------
        if ("out" === direct_preposition) {
          this.game.debug(`F1339 | ${this.name}.js | preposition is out`);

          msg += `$(We) ${
            input.input_verb
          } ${player.getPrettyUnnestPreposition()} ${nest_asset.articlename}. `;

          results = player.onUnnestThisFromThat(nest_asset);
          if ("undefined" !== typeof results) return results;

          player.posture = "stand";
        } // out

        // off or down ----------
        else if (
          "off" === direct_preposition ||
          "down" === direct_object.direction
        ) {
          this.game.debug(
            `F1122 | ${this.name}.js | preposition is off or direction is down`
          );

          msg += `$(We) ${
            input.getVerb() === "release" ? "let go" : "climb off"
          } of ${nest_asset.articlename} and ${
            player.position.y > 1 ? "fall" : "drop"
          } to the ground. `;

          results = player.onUnnestThisFromThat(nest_asset);
          if ("undefined" !== typeof results) return results;

          if (direct_object.getY() < 0) player.setY(direct_object.getY());
          else player.setY(0);

          player.posture = "stand";
        } // off or down

        // up ----------
        // @TODO does this ever fire?
        else if (direct_object && "up" === direct_object.direction) {
          // do we need to climb?

          this.game.debug(`F1135 | ${this.name}.js | preposition is up`);
          msg += `$(We) climb ${player.getPrettyUnnestPreposition()} ${
            direct_object.articlename
          }. `;

          results = player.onUnnestThisFromThat(nest_asset);
          if ("undefined" !== typeof results) return results;

          player.posture = "stand";
        } // up

        // not off or out or up or down ----------
        else {
          /**
           * The phrase "get in" is vague about both the preposition,
           * which could mean "in" or "on", and the posture,
           * which could mean "sit" or "lie" or "stand".
           * So we check player.preposition
           * and player.posture for context.
           */

          posture = direct_object.aspects[direct_preposition].player.posture;
          preposition =
            direct_object.aspects[direct_preposition].player.preposition;

          this.game.debug(
            `F1139 | ${this.name}.js | preposition is not off or out or up or down`
          );
          msg += `$(We) ${posture} ${preposition} ${direct_object.articlename}. `;

          results = player.onNestThisToThat(direct_object, preposition);
          if ("undefined" !== typeof results) return results;

          player.setY(direct_object.getYTop());
          player.posture = posture;
        } // not off or out or up or down
      } // verb noun

      // sentence structure: verb noun preposition noun ----------
      // ex: go east on bike, go east on ledge
      // go up with piton, go up with jetpack

      if (input.hasStructure("verb noun preposition noun")) {
        // assume that any sentence in this structure is in form of
        // "go east on bike" where noun is a direction

        results = this.game.tryTravel(direct_object.direction, {
          with: [indirect_object.id],
        });
        if (A.isFalseOrNull(results)) return results;
        // no msg because tryTravel handled it
      } // verb noun preposition noun

      // ---------- sentence structure: verb preposition noun preposition noun

      if (
        input.hasStructure("verb preposition noun preposition noun") ||
        input.hasStructure(
          "verb preposition noun preposition noun preposition noun"
        )
      ) {
        // from/to ----------

        if (input.verb_params.found.fromto) {
          this.game.debug(`F1866 | ${this.name}.js | go from to `);

          posture = indirect_object.aspects.on.player.posture;

          msg += `$(We) ${
            input.verb_params.with
              ? "use " +
                (indirect_object2
                  ? indirect_object2.articlename
                  : indirect_object.articlename) +
                " to"
              : ""
          } climb from ${direct_object.articlename} to ${
            indirect_object.articlename
          } and ${posture} ${indirect_object.default_aspect} it. `;

          // if player is nested, unnest
          if (nest_asset.id === direct_object.id) {
            results = player.onUnnestThisFromThat(direct_object);
            if ("undefined" !== typeof results) return results;
          }

          // if it isn't already nested, nest it
          if (nest_asset.id !== indirect_object.id) {
            results = player.onNestThisToThat(
              indirect_object,
              indirect_object.default_aspect
            );
            if ("undefined" !== typeof results) return results;
            player.posture = posture;
          }

          // let range = indirect_object.getYRange();
          // if (newY < range.min) newY = range.min;
          // else if (newY > range.max) newY = range.max;
          // player.setY(newY);
        } // fromto
      } // verb preposition noun preposition noun

      this.handleSuccess(msg, direct_object);
      return true;
    },

    checkWith: function (direct_preposition, direct_object, indirect_object) {
      // if we're here, indirect_preposition is "with"
      let response = { failure: false, return: null, msg: "" };

      // works with any indirect object?
      if (direct_object.DOVallowWithAnything(this.name)) {
        return { return: true };
      }

      // indirect object not required?
      if (direct_object.DOVallowWithNothing(this.name)) {
        this.game.debug(
          `F1868 | ${this.name}.js | ${direct_object.id} can't be ${this.state} with ${indirect_object.id} `
        );
        response.msg += `${indirect_object.Articlename} won't help $(us) ${this.name} ${direct_preposition} ${direct_object.articlename}. `;
        response.failure = true;
        return response;
      }

      // indirect object usable with direct object?
      if (!direct_object.DOVallowWithAsset(this.name, indirect_object)) {
        this.game.debug(
          `F1869 | ${this.name}.js | ${direct_object.id}.dov.${this.name}.with_assets does not include ${indirect_object.id} `
        );
        response.msg += `${indirect_object.Articlename} can't be used to ${
          this.game.getInput().input_verb
        } ${direct_preposition} ${direct_object.articlename}. `;
        response.failure = true;
        return response;
      }

      // single use indirect object?
      if (
        indirect_object.IOVallowOnce(this.name) &&
        indirect_object.IOVdidDo(this.name)
      ) {
        this.game.debug(
          `F1870 | ${this.name}.js | ${indirect_object.id}.iov.${
            this.name
          }.once and ${indirect_object.id}.iov.${this.name}.do_count is ${
            indirect_object.iov[this.name].do_count
          } `
        );
        response.msg += `${indirect_object.Articlename} has already been used to ${this.name} something. `;
        response.failure = true;
        return response;
      }

      return response;
    },

    canPlayerGoFromThisToThat: function (direct_object, indirect_object) {
      // if we're here, direct_preposition is "from" and indirect_preposition is "to"
      let response = { failure: false, return: null, msg: "" };
      let input = this.game.getInput();
      let player = this.game.getPlayer();
      let nest_asset = player.getNestAsset();
      let nest_preposition = player.getNestPreposition();

      if (!nest_asset || nest_asset.id !== direct_object.id) {
        // from noun must be the nest
        this.game.debug(
          `F1183 | ${this.name}.js | ${nest_asset.id} !== ${direct_object.id}`
        );
        response.msg += `$(We're) not ${
          direct_object.default_aspect &&
          direct_object.aspects[direct_object.default_aspect].player.can.enter
            ? direct_object.default_aspect
            : "on"
        } ${direct_object.articlename}. `;
        response.failure = true;
        return response;
      } // input.verb_params.found.fromto

      let reachable = true; // default = reachable

      if (this.game.settings.xz_determines_reachability) {
        // is distance between assets greater than jump_length?
        let distance = A.getHorizontalDistance(
          direct_object.position,
          indirect_object.position
        );
        reachable = distance <= this.game.settings.jump_length;
        // @TODO doSuccess handling for this
      }
      if (!reachable) {
        this.game.debug(
          `F1864 | ${this.name}.js | player is nested ${nest_preposition} ${nest_asset.id}, infer "go from to" `
        );
        response.msg += `$(We) can't ${input.input_verb} ${
          indirect_object.default_aspect
        } ${
          indirect_object.articlename
        } while ${player.getPostureGerund()} ${nest_preposition} ${
          direct_object.articlename
        }. `;

        response.failure = true;
        return response;
      }

      return response;
    },
  };
})(); // go