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

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

  /**
   * @augments {adventurejs.Verb}
   * @class climb
   * @ajsnode game.dictionary.verbs.climb
   * @ajsconstruct MyGame.createVerb({ "name": "climb", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading LocomotionVerbs
   * @summary Verb meaning climb, as in "climb tree"; or, travel in specified direction.
   * @ajssynonyms climb onto, climb on, climb up
   * @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; climb tree</span>
   * You climb a good way into the tree before your climbing
   * is interrupted by an angry squirrel. Apparently you've
   * put a foot into the knot in which it stores its nuts.
   * </pre>
   * <p>
   * <strong>Climb</strong> can operate with or without
   * a noun, inferring from context whether the action can succeed.
   * </p>
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.climb = {
    name: "climb",
    prettyname: "climb",
    past_tense: "climbed",
    synonyms: ["climb"],
    type: { locomotion: true, travel: true },

    verb_prep_noun: [
      // parse climb up / climb down as climb
      // to prevent up/down being treated as nouns
      //"climb down",
      //"climb up",
    ],

    /**
     * @ajsverbstructures
     * @memberof climb
     */
    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_under: true,
      //not_behind: true,
    },

    /**
     * @memberof climb
     * @ajsverbphrase
     * phrase1:
     * {
     *   accepts_noun: true,
     *   accepts_preposition: true,
     *   accepts_direction: 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,
      accepts_direction: true,
      noun_must_be: {
        tangible: true,
        known: true,
        present: true,
        visible: true,
        reachable: true,
      },
    },

    /**
     * @memberof climb
     * @ajsverbphrase
     * phrase2:
     * {
     *   accepts_noun: true,
     *   accepts_preposition: true,
     *   noun_must_be:
     *   {
     *     tangible: true,
     *     known: true,
     *     present: true,
     *     visible: true,
     *     reachable: true,
     *   },
     * },
     */
    phrase2: {
      accepts_noun: true,
      noun_must_be: {
        tangible: true,
        known: true,
        present: true,
        visible: true,
        reachable: true,
      },
      accepts_preposition: true,
      accepts_these_prepositions: ["with", "to"],
    },

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

    doTry: function () {
      var input = this.game.getInput();
      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 player = this.game.getPlayer();
      var nest_preposition = player.getNestPreposition();
      var nest_asset = player.getNestAsset();
      var msg = "";
      var try_go, up, down, fromto;

      if (direct_object?.is.global) {
        // @TODO floor handling
        this.game.debug(
          `F1724 | ${this.name}.js | ${direct_object.id}.is.global `,
        );
        msg += `$(We) can't climb ${direct_object.articlename}. `;
        this.handleFailure(msg);
        return null;
      }

      // doVerb go
      // @TODO what if you're half way up a wall and try to climb east on the wall?

      try_go = direct_object?.direction;
      if (
        direct_preposition &&
        -1 !== ["in", "out", "under", "behind"].indexOf(direct_preposition)
      ) {
        // ie climb in, climb under, climb behind
        try_go = true;
      }
      if (try_go) {
        this.game.debug(`F1190 | ${this.name}.js | infer 'go', doVerb go`);
        this.game.dictionary.doVerb("go");
        //this.game.tryTravel(direct_preposition);
        return null;
      }

      // sentence structure: verb ----------
      // ex: climb
      if (input.hasStructure("verb")) {
        // if climb was entered without any preposition or object,
        // assume up and try to get an object
        if (nest_asset) {
          // up or down?
          let h = nest_asset.dimensions.height;
          let y = nest_asset.position.y;
          let py = player.position.y;
          if (py >= y + h) input.setPreposition(1, "down");
          else input.setPreposition(1, "up");

          // update input
          input.setAsset(1, nest_asset);
          input.setStructure("verb preposition noun");
          direct_object = input.getAsset(1);
          direct_preposition = input.getPreposition(1);
        } else {
          // prompt for a direct object
          input.setSoftPrompt({ noun1: true });
          this.game.debug(
            `F1177 | ${this.name}.js | no noun provided or inferrable, soft prompt noun1`,
          );
          msg += `What would $(we) like to climb? `;
          this.handleFailure(msg);
          return null;
        }
      } // verb

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

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

      // sentence structure: verb preposition ----------
      // climb down, climb up
      // try to get a noun
      if (input.hasStructure("verb preposition")) {
        // was nest asset the implied noun?
        if (nest_asset) {
          input.setAsset(1, nest_asset);
          input.setStructure(input.getStructure() + " noun");
          direct_object = input.getAsset(1);

          if (down && !nest_asset.is.climbable) {
            input.setPreposition(1, "off");
            direct_preposition = "off";
            this.game.debug(
              `F1206 | ${this.name}.js | infer 'climb down ${nest_asset.id} `,
            );
          }
        }

        if (!nest_asset) {
          if (down) {
            // if player is not nested then "climb down" means "down"
            this.game.debug(
              `F1186 | ${this.name}.js | infer 'go down', tryTravel down`,
            );
            this.game.tryTravel("down");
            return null;
          }

          if (up) {
            this.game.debug(
              `F1193 | ${this.name}.js | infer 'go up', tryTravel up`,
            );
            this.game.tryTravel("up");
            return null;
          }
        }
      } // verb preposition

      // sentence structure: verb noun ----------
      // ex: climb tree, climb east
      // try to get a preposition
      if (input.hasStructure("verb noun")) {
        // is player already on noun?
        // set preposition to nest_preposition
        if (nest_asset && nest_asset.id === direct_object.id) {
          direct_preposition = nest_preposition;
          input.setPreposition(1, direct_preposition);
          input.setStructure("verb preposition noun");
        }

        // is player not on noun?
        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);
          input.setStructure("verb preposition noun preposition noun");
          direct_object = input.getAsset(1);
          indirect_object = input.getAsset(2);
          direct_preposition = "from";
          indirect_preposition = "to";
        }
      } // verb noun

      // sentence structure: verb preposition noun ----------
      // ex: climb on tree
      // verify preposition and noun
      if (input.hasStructure("verb preposition noun")) {
        if (direct_preposition === "to") {
          // "climb to" is used for climbing from one
          // nest to another. If player isn't nested,
          // it's just regular "climb".
          if (!nest_asset) {
            input.setInPhrase(1, "preposition", "on");
            if (direct_object.position.y >= player.position.y) up = true;
            else down = true;
          }

          if (nest_asset && nest_asset.id === direct_object.id) {
            this.game.debug(
              `F1192 | ${this.name}.js | player is nested on ${direct_object.id} `,
            );
            msg += `$(We're) already on ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }

          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);
            fromto = true;
            input.setStructure("verb preposition noun preposition noun");
          }
        } // to

        if (down && !nest_asset) {
          // @TODO this is a quick patch - need more handling here
          // could be my favorite example of a ladder in a hole

          if (direct_object.position.y >= player.position.y) {
            this.game.debug(
              `F1718 | ${this.name}.js | player is not on ${direct_object.id} `,
            );
            msg += `$(We're) not on ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }

          // @TODO should this return true? do we still have more checking to do?
        }

        if (down && nest_asset) {
          if (
            direct_object.id === nest_asset.id &&
            direct_object.is.unleavable
          ) {
            this.game.debug(
              `F1184 | ${this.name}.js | ${direct_object.id}.is.unleavable is true `,
            );
            msg += `$(We) can't leave ${direct_object.articlename} that way. `;
            this.handleFailure(msg);
            return null;
          }
          // player is not on direct_object
          if (direct_object.id !== nest_asset.id) {
            this.game.debug(
              `F1185 | ${this.name}.js | player is not on ${direct_object.id} `,
            );
            msg += `$(We're) not on ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }
        } // down

        if (up && nest_asset && nest_asset.id !== direct_object.id) {
          // it's fromto
          input.swapPhrases(1, 2);
          input.setPreposition(1, "from");
          input.setPreposition(2, "to");
          input.setAsset(1, nest_asset);
          input.setStructure("verb preposition noun preposition noun");
          direct_object = input.getAsset(1);
          indirect_object = input.getAsset(2);
          direct_preposition = "from";
          indirect_preposition = "to";
        }

        if (up && nest_asset && nest_asset.id === direct_object.id) {
          if (
            "on" === nest_preposition &&
            player.position.y >= nest_asset.getYTop()
          ) {
            // player is already on target
            // and can't climb any higher
            this.game.debug(
              `F1178 | ${this.name}.js | player.position.y >= ${direct_object.id}.dimensions.height `,
            );
            msg += `$(We) can't climb any higher on ${direct_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }
        }

        // player is nested some other way with target
        // and needs to unnest first
        if (up && nest_asset && "on" !== nest_preposition) {
          this.game.debug(
            `F1179 | ${this.name}.js | player is ${nest_preposition} ${nest_asset.id} `,
          );
          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 (
          (up && !direct_object.isDOV(this.name)) ||
          !direct_object.canPlayerNest("on")
        ) {
          this.game.debug(
            `F1196 | ${this.name}.js | !${direct_object.id}.dov.${this.name} or !${direct_object.id}.aspects.on.player.can.enter `,
          );
          msg += `$(We) can't climb ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        } // up
      } // verb preposition noun

      // sentence structure: verb noun preposition noun ----------
      if (input.hasStructure("verb noun preposition noun")) {
        // example: 'climb cliff with pick'
        if ("with" !== indirect_preposition && "to" !== indirect_preposition) {
          // already accounted for this in accepts_these_prepositions
          this.game.debug(`F1856 | ${this.name}.js | irregular phrase `);
          msg += this.game.parser.getUnparsedMessage(input.input);
          this.handleFailure(msg);
          return null;
        }

        // to ----------

        if ("to" === indirect_preposition) {
          // we got something like "climb tree to window"
          // we're going to treat it like "climb from tree to window"
          input.setPreposition(1, "from");
          direct_preposition = "from";
          input.setStructure("verb preposition noun preposition noun");
        }

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

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

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

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

          // indirect object usable with direct object?
          if (!direct_object.DOVallowWithAsset(this.name, indirect_object)) {
            this.game.debug(
              `F1858 | ${this.name}.js | ${direct_object.id}.dov.${this.name}.with_assets does not include ${indirect_object.id} `,
            );
            msg += `$(We) can't ${this.name} ${direct_object.articlename} ${indirect_preposition} ${indirect_object.articlename}. `;
            this.handleFailure(msg);
            return null;
          }

          // single use indirect object?
          if (
            indirect_object.IOVallowOnce(this.name) &&
            indirect_object.IOVdidDo(this.name)
          ) {
            this.game.debug(
              `F1859 | ${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
              } `,
            );
            msg += `${indirect_object.Articlename} has already been used to ${this.name} something. `;
            this.handleFailure(msg);
            return null;
          }
        } // with
      } // verb noun preposition noun

      // sentence structure: verb preposition noun preposition noun
      // ex: climb from tree to roof
      if (input.hasStructure("verb preposition noun preposition noun")) {
        // climb from a to b is the only phrase we accept in this form

        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);
        }

        if (!fromto)
          fromto =
            direct_preposition === "from" && indirect_preposition === "to";

        if (!fromto) {
          // "climb from to" is the only variant that can handle 2 nouns
          this.game.debug(
            `F1180 | ${this.name}.js | this phrase and sentence structure not supported `,
          );
          msg += this.game.parser.getUnparsedMessage(input.input);
          this.handleFailure(msg);
          return null;
        }

        if (fromto && (!nest_asset || nest_asset.id !== direct_object.id)) {
          // from noun must be the nest
          this.game.debug(
            `F1187 | ${this.name}.js | ${nest_asset.id} !== ${direct_object.id}`,
          );
          msg += `$(We're) not on ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        } // fromto

        // @TODO can player reach iobj from nest?
        if (fromto /*&& direct_object.id !== indirect_object.id*/) {
          this.game.debug(`F1853 | ${this.name}.js | change to fromto `);
          msg += `$(We) can't climb to ${indirect_object.articlename} from ${nest_preposition} ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      } // verb preposition noun preposition noun

      // save our findings for doSuccess so we don't have to repeat this logic
      if (up) input.verb_params.up = true;
      else if (down) input.verb_params.down = true;
      else if (fromto) input.verb_params.fromto = true;

      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var direct_object = input.getAsset(1);
      var indirect_object = input.getAsset(2);
      var player = this.game.getPlayer();
      var nest_asset = player.getNestAsset();
      var top = direct_object.getYTop();
      var bottom = direct_object.getYBottom();
      var direct_preposition = input.getPreposition(1);
      var indirect_preposition = input.getPreposition(2);
      var preposition = "on";
      var posture = direct_object.aspects[preposition].player.posture;
      var newY = 0;
      var newPoint = { y: 0 };
      var grounded;
      var msg = "";
      var results;

      this.game.debug(`F1181 | ${this.name}.js | doSuccess `);
      this.game.debug(
        `F1340 | ${this.name}.js | start y is ${player.position.y} `,
      );

      // up ----------

      if (input.verb_params.up) {
        this.game.debug(`F1374 | ${this.name}.js | climb up `);

        if (
          direct_object.dimensions.height <= this.game.settings.reach_height
        ) {
          // default posture for "climb on [short thing like furniture]" is stand
          posture = direct_object.aspects[preposition].player.can.stand
            ? "stand"
            : direct_object.aspects[preposition].player.posture;
          msg += `$(We) ${
            input.verb_params.with
              ? "use " + indirect_object.articlename + " to"
              : ""
          } climb up ${
            direct_object.articlename
          } and ${posture} ${preposition} it. `;
        } else {
          newY =
            top - player.position.y >= this.game.settings.reach_height
              ? player.position.y + this.game.settings.reach_height
              : top;
          msg += `$(We) ${
            input.verb_params.with
              ? "use " + indirect_object.articlename + " to"
              : ""
          } climb `;

          let percent = newY / top;
          if (percent === 1) msg += "to the top of ";
          else if (percent > 0.5) msg += "further up ";
          else msg += "part way up ";

          msg += `${direct_object.articlename}. `;
        }

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

        player.position.y = newY;
      } // up

      // down ----------

      if (input.verb_params.down) {
        this.game.debug(`F1123 | ${this.name}.js | climb down `);

        // climbing down a thing with its bottom above the ground, like a stalactite
        if (bottom > 0 && player.position.y > bottom) {
          newY =
            player.position.y - bottom >= this.game.settings.reach_height
              ? player.position.y - this.game.settings.reach_height
              : bottom;

          msg += `$(We) ${
            input.verb_params.with
              ? "use " + indirect_object.articlename + " to"
              : ""
          } climb downward on ${direct_object.articlename}. `;
        }

        // player is already at bottom of thing that is above the ground
        else if (bottom > 0 && player.position.y === bottom) {
          msg += `$(We) fall to the ground. `;
          newY = 0;
          grounded = true;
          // TODO gravity
        }

        // climbing down a thing rooted to the ground
        else if (bottom === 0) {
          newY =
            player.position.y - bottom >= this.game.settings.reach_height
              ? player.position.y - this.game.settings.reach_height
              : bottom;

          msg += `$(We) ${
            input.verb_params.with
              ? "use " + indirect_object.articlename + " to"
              : ""
          } climb `;

          if (newY > 0) {
            msg += `downward on `;
          } else {
            msg += `down off `;
            grounded = true;
          }

          msg += `${direct_object.articlename}. `;
        }

        // climbing down a thing below ground level
        // @todo what happens at the bottom?
        // at the moment we're left, stuck at the bottom of the thing
        else if (0 > bottom) {
          newY =
            player.position.y - bottom >= this.game.settings.reach_height
              ? player.position.y - this.game.settings.reach_height
              : bottom;

          msg += `$(We) ${
            input.verb_params.with
              ? "use " + indirect_object.articlename + " to"
              : ""
          } climb ${newY > bottom ? "further down " : "to the bottom of "}`;

          if (newY > bottom) {
            msg += `further down `;
          } else {
            msg += `to the bottom of `;
          }

          msg += `${direct_object.articlename}. `;
        }

        player.position.y = newY;
        if (grounded) {
          results = player.onUnnestThisFromThat(nest_asset);
          if ("undefined" !== typeof results) return results;
          player.posture = "stand";
        }
      } // down

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

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

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

        // msg += `$(We) ${
        //   input.verb_params.with
        //     ? "use " + indirect_object.articlename + " to"
        //     : ""
        // } climb from ${direct_object.articlename} to ${
        //   indirect_object.articlename
        // } and ${posture} ${preposition} it. `;
        msg += `$(We) climb from ${direct_object.articlename} to ${indirect_object.articlename} and ${posture} ${preposition} 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, preposition);
          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

      this.game.debug(`F1694 | climb.js | end y is ${player.position.y} `);

      // print output
      this.handleSuccess(msg, direct_object);
      return true;
    },
  }; // END p.preverbs.push
})();