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

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

  /**
   * @augments {adventurejs.Verb}
   * @class throw
   * @ajsnode game.dictionary.verbs.throw
   * @ajsconstruct MyGame.createVerb({ "name": "throw", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading ManipulationVerbs
   * @param {Object} parsedNoun One word user input string.
   * @summary Verb meaning throw, as in "throw boulder at giant".
   * @ajssynonyms throw, toss
   * @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; throw flugelhorn</span>
   * You throw the jewel encrusted flugelhorn. It emits a sad little blat.
   * </pre>
   * <p>
   * <strong>throw</strong> a {@link adventurejs.Tangible|Tangible}
   * {@link adventurejs.Asset|Asset} or
   * {@link adventurejs.Substance|Substance}.
   * If a Tangible Asset is thrown, requires that the Asset is
   * in player's inventory. Substances may be thrown from containers
   * in inventory or from reachable containers.
   * </p>
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.throw = {
    name: "throw",
    prettyname: "throw",
    past_tense: "threw",
    synonyms: ["throw", "toss"],
    gerund: "throwing",

    /**
     * Though "verb noun preposition noun preposition noun" is accepted,
     * it is only used to handle "throw substance from container at thing".
     * @ajsverbstructures
     * @memberof throw
     */
    accepts_structures: [
      "verb noun",
      "verb noun preposition",
      "verb noun preposition noun",
      "verb noun preposition noun preposition noun",
    ],

    player_must_be: {
      not_constrained: true,
    },

    /**
     * @memberof throw
     * @ajsverbphrase
     * phrase1:
     * {
     *   accepts_noun:true,
     *   requires_noun: true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present: true,
     *     reachable: true,
     *     visible: true,
     *     in_hands_unless_reservoir: true,
     *   },
     * },
     */
    phrase1: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        known: true,
        matter: true,
        present: true,
        reachable: true,
        visible: true,
        in_hands_unless_reservoir: true,
      },
    },

    /**
     * @memberof throw
     * @ajsverbphrase
     * phrase2:
     * {
     *   accepts_noun:true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present_if_tangible: true,
     *   },
     *   accepts_preposition: true,
     *   requires_preposition: true,
     * },
     */
    phrase2: {
      accepts_noun: true,
      noun_must_be: {
        known: true,
        matter: true,
        present: true,
        // present_if_tangible: true,
      },
      accepts_preposition: true,
      accepts_preposition_without_noun: true,
      requires_preposition: true,
    },

    /**
     * @memberof throw
     * @ajsverbphrase
     * phrase3:
     * {
     *   accepts_noun:true,
     *   noun_must_be:
     *   {
     *     matter: true,
     *     present_if_tangible: true,
     *   },
     *   accepts_preposition: true,
     *   requires_preposition: true,
     * },
     */
    phrase3: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        known: true,
        matter: true,
        present: true,
        // present_if_tangible: true,
      },
      accepts_preposition: true,
      requires_preposition: true,
    },

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

    doTry: function () {
      var input = this.game.getInput();
      var verb_phrase = input.verb_phrase;
      var asset1 = input.getAsset(1);
      var substance1 = input.getSubstance(1);
      var asset2 = input.getAsset(2);
      var substance2 = input.getSubstance(2);
      var preposition2 = input.getPreposition(2);
      var asset3 = input.getAsset(3);
      var preposition3 = input.getPreposition(3);
      var substance3 = input.getSubstance(3);
      var player = this.game.getPlayer();
      var currentRoom = this.game.getCurrentRoom();
      var msg = "";
      var results;
      var inhands;

      var thrown_asset;
      var thrown_substance;
      var thrown_tangible;

      var target_asset;
      var target_substance;
      var target_tangible;
      var target_preposition;

      var assist_asset;
      var assist_preposition;

      if (player.getNestId() === asset1.id) {
        this.game.debug(
          `D1211 | ${this.name}.js | player is nested on ${asset1.id}`
        );
        msg += `$(We) can't ${this.name} ${asset1.articlename} ${
          preposition2 ? preposition2 : ""
        } ${
          asset2 ? asset2.articlename : ""
        } while $(we're) ${player.getPostureGerund()} ${player.getNestPreposition()} it. `;
        this.handleFailure(msg);
        return null;
      }

      // did player input "throw substance from container"?
      // if so, selection handlers may have inferred asset1
      // and bypassed asset2 contents check
      if (substance1 && preposition2 === "from") {
        // we bypassed reachable/visible tests for asset2&3

        // is container reachable?
        if (!this.game.parser.selectReachable(asset2.id).length) {
          this.game.debug(
            `D1612 | ${this.name}.js | ${asset2.id} is not reachable `
          );
          msg += `$(We) can't reach ${substance1.articlename}`;
          msg += `${
            asset2 instanceof adventurejs.Room ? "" : " in" + asset2.articlename
          }`;
          msg += `. `;
          this.handleFailure(msg);
          return null;
        }

        // is container visible?
        if (!this.game.parser.selectVisible(asset2.id).length) {
          this.game.debug(
            `D2134 | ${this.name}.js | ${asset2.id} is not visible `
          );
          msg += `$(We) can't see ${substance1.articlename}`;
          msg += `${
            asset2 instanceof adventurejs.Room ? "" : " in" + asset2.articlename
          }`;
          msg += `. `;
          this.handleFailure(msg);
          return null;
        }

        // does container contain substance?
        if (!asset2.containsSubstance(substance1.id)) {
          this.game.debug(
            `D2100 | ${this.name}.js | ${asset2.id} doesn't contain ${substance1.id} `
          );
          msg += `${asset2.Articlename} doesn't contain ${substance1.id}. `;
          this.handleFailure(msg);
          return null;
        }

        // did selection handler infer a different
        // container from the one specified by player?
        if (asset1.id === asset2.id) {
          // restructure the sentence
          input.setAsset(1, asset2);
          input.deletePhrase(2);
          asset1 = input.getAsset(1);
          asset2 = input.getAsset(2);
          preposition2 = input.getPreposition(2);
          asset3 = false;
          preposition3 = false;
        }
      } // substance1 && preposition2 === "from"

      // did player input "throw a with b at c"?
      // prefer "throw a at c with b"
      if (preposition2 === "with" && asset3) {
        input.swapPhrases(2, 3);
        asset2 = input.getAsset(2);
        preposition2 = input.getPreposition(2);
        asset3 = input.getAsset(3);
        preposition3 = input.getPreposition(3);
      }

      // sentence structure: verb noun preposition
      // ex: throw ball up
      // @TODO what about "throw rock east" ?
      if (input.hasStructure("verb noun preposition")) {
        if (["up", "down"].includes(preposition2)) {
          input.deletePhrase(2);
          preposition2 = false;
        } else {
          this.game.debug(
            `D1956 | ${this.name}.js | no handling for ${this.name} ${thrown_asset.id} ${preposition2} `
          );
          msg += `$(We) don't know how to ${this.name} ${thrown_asset.id} ${preposition2}. `;
          this.handleFailure(msg);
          return null;
        }
      }

      // sentence structure: verb noun
      // ex: throw ball
      if (
        input.hasStructure("verb noun") &&
        thrown_asset instanceof adventurejs.Tangible
      ) {
        target_asset = target_tangible = currentRoom;
      }

      // we should be able to determine these now
      thrown_substance = substance1;
      thrown_tangible = asset1;
      if (preposition2 === "with") {
        assist_asset = asset2;
        assist_preposition = preposition2;
      } else {
        target_substance = substance2;
        target_tangible = asset2 || currentRoom;
        if (target_tangible.hasClass("Floor")) {
          target_tangible = currentRoom;
        }
        if (target_tangible.hasClass("Room")) {
          target_preposition = target_tangible.default_aspect;
        }
        if (!target_tangible.hasClass("Room")) {
          if (["in", "on", "to", "at"].includes(preposition2)) {
            target_preposition = target_tangible.default_aspect || "at";
          } else {
            target_preposition = preposition2 || "at";
          }
        }
      }
      if (preposition3 === "with") {
        assist_asset = asset3;
        assist_preposition = preposition3;
      }
      thrown_asset = thrown_substance || thrown_tangible;
      target_asset = target_substance || target_tangible;

      input.verb_params = {
        thrown_asset: thrown_asset,
        thrown_substance: thrown_substance,
        thrown_tangible: thrown_tangible,
        target_asset: target_asset,
        target_substance: target_substance,
        target_tangible: target_tangible,
        target_preposition: target_preposition,
        assist_asset: assist_asset,
        assist_preposition: assist_preposition,
      };

      if (!thrown_asset?.isDOV(this.name)) {
        this.game.debug(
          `D1495 | ${this.name}.js | ${thrown_asset.id}.dov.${this.name}.enabled is false `
        );
        msg += `$(We) can't ${this.name} ${thrown_asset.articlename}. `;
        this.handleFailure(msg);
        return null;
      }

      // single use object?
      if (
        thrown_asset.allowVerbOnce(this.name, "dov") &&
        thrown_asset.didVerb(this.name, "dov")
      ) {
        this.game.debug(
          `D2108 | ${this.name}.js | ${thrown_asset.id}.dov.${this.name}.once and ${thrown_asset.id}.did.${this.name}.directly `
        );
        msg += `$(We) can't ${this.name} ${thrown_asset.articlename} again. `;
        this.handleFailure(msg);
        return false;
      }

      // throwing tangible?
      if (thrown_asset instanceof adventurejs.Tangible) {
        // we bypassed the usual in_hands check to allow
        // substance handling so we need to check that now
        // @TODO automatically pick thing up?
        inhands = this.game.parser.selectInHands(thrown_asset.id);
        // at least from inventory?
        // inhands = this.game.parser.selectInInventoryIfTakeable(thrown_asset.id);
        if (!inhands.length) {
          this.game.debug(
            `D1601 | ${this.name}.js | ${thrown_asset.id} not in player's hands `
          );
          msg += `$(We're) not holding ${thrown_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      }

      if (assist_asset) {
        // is player holding assist_asset?
        if (
          assist_asset.isDOV("take") &&
          !this.game.parser.selectInHands(assist_asset.id).length
        ) {
          this.game.debug(
            `D2102 | ${this.name}.js | ${assist_asset.id}.$is("inhands") is false `
          );
          msg += `$(We're) not holding ${assist_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
        // can player reach assist_asset?
        if (
          !assist_asset.isDOV("take") &&
          !this.game.parser.selectReachable(assist_asset.id).length
        ) {
          this.game.debug(
            `D2103 | ${this.name}.js | ${assist_asset.id}.$is("reachable") is false `
          );
          msg += `$(We) can't reach ${assist_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // works with any indirect object?
        if (thrown_asset.allowVerbWithAnything(this.name, "dov")) {
          return true;
        }

        // indirect object not required?
        // if( thrown_asset.allowVerbWithNothing(this.name, "dov") )
        // {
        //   this.game.debug(` | ${this.name}.js | ${thrown_asset.id}.dov.${this.name}.with_nothing `);
        //   msg += `$(We) can't ${this.name} ${thrown_asset.articlename} ${assist_preposition} ${assist_asset.articlename}. `;
        //   this.handleFailure(msg);
        //   return null;
        // }

        // indirect object2 usable with direct object?
        if (!thrown_asset.allowVerbWithAsset(this.name, assist_asset, "dov")) {
          this.game.debug(
            `D2104 | ${this.name}.js | ${thrown_asset.id}.dov.${this.name}.with_assets/with_classes does not include ${assist_asset.id} `
          );
          msg += `$(We) can't ${this.name} ${thrown_asset.articlename} ${preposition3} ${assist_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // can indirect object be used?
        if (!assist_asset.isIOV(this.name)) {
          this.game.debug(
            `D2106 | ${this.name}.js | ${assist_asset.id}.iov.${this.name}.enabled is false `
          );
          msg += `$(We) can't ${this.name} anything ${preposition3} ${assist_asset.articlename}. `;
          this.handleFailure(msg);
          return false;
        }

        // single use indirect object?
        if (
          assist_asset.allowVerbOnce(this.name, "iov") &&
          assist_asset.iDidVerb(this.name, "iov")
        ) {
          this.game.debug(
            `D2107 | ${this.name}.js | ${assist_asset.id}.iov.${
              this.name
            }.once and ${assist_asset.id}.did.${this.name}.indirectly is ${
              assist_asset.did[this.name].indirectly
            } `
          );
          msg += `${assist_asset.Articlename} has already been used to ${this.name} something. `;
          this.handleFailure(msg);
          return null;
        }
      }

      // @TODO this seems of questionable relevance now
      // sentence structure: verb noun preposition noun preposition noun
      if (input.hasStructure("verb noun preposition noun preposition noun")) {
        // throw accepts something like: throw water balloon at nancy with slingshot
        if (!(preposition2 !== "with" && preposition3 === "with")) {
          // otherwise we don't know how to handle this
          this.game.debug(
            `D2106 | ${this.name}.js | ${this.name} ${asset1.id} ${preposition2} ${asset2.id} ${preposition3} ${asset3.id} not handled `
          );
          msg += `$(We) don't know how to ${this.name} ${asset1.articlename} ${preposition2} ${asset2.articlename} ${preposition3} ${asset3.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      }

      // @TODO save reconfigured assets back to input.verb_params

      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var verb_phrase = input.verb_phrase;
      var currentRoom = this.game.getCurrentRoom();
      var player = this.game.getPlayer();
      var msg = "";
      var results;
      var mixer;
      var quantity = 0;

      var thrown_asset = input.verb_params.thrown_asset;
      var thrown_tangible = input.verb_params.thrown_tangible;
      var thrown_substance = input.verb_params.thrown_substance;
      var thrown_vessel;
      if (thrown_substance)
        thrown_vessel = thrown_tangible.getVesselAt(
          thrown_tangible.getVesselPreposition()
        );

      var target_asset = input.verb_params.target_asset;
      var target_tangible = input.verb_params.target_tangible;
      var target_substance = input.verb_params.target_substance;
      var target_vessel;
      if (target_substance)
        target_vessel = target_tangible.getVesselAt(
          target_tangible.getVesselPreposition()
        );
      var target_preposition = input.verb_params.target_preposition;

      if (!target_tangible) {
        target_tangible = currentRoom;
        target_preposition = currentRoom.default_aspect;
      }

      var bounce_target = target_tangible;
      var bounce_preposition = target_preposition;
      var bounce_inferred;

      var assist_asset = input.verb_params.assist_asset;
      var assist_preposition = input.verb_params.assist_preposition;

      if (thrown_asset.hasClass("Tangible")) {
        if (thrown_asset.isWithin(player)) {
          results = player.onRemoveThatFromThis(thrown_asset);
          if ("undefined" !== typeof results) return results;
        }
        results = this.tryToPutThisInThatAspectOrParent(
          thrown_asset,
          target_preposition,
          target_tangible
        );
        bounce_target = results.indirect_object;
        bounce_preposition = results.indirect_preposition;
        bounce_inferred = true;
        results = bounce_target.onMoveThatToThis(
          thrown_asset,
          bounce_preposition
        );
        if ("undefined" !== typeof results) return results;
      }

      // throwing substance?
      if (thrown_asset.hasClass("Substance")) {
        // is player holding container?
        if (thrown_tangible.isWithin(player)) {
          if (thrown_vessel.getVolume() === Infinity) {
            // if container is infinite, try to get volume of flow
            quantity = thrown_vessel.volume_of_flow_per_turn;
          } else {
            // otherwise empty container
            quantity = thrown_vessel.getVolume();
            thrown_vessel.empty();
          }
        } else {
          // if player is not holding the container, subtract a handful
          quantity = this.game.settings.handful;
          if (thrown_vessel.volume !== Infinity) {
            thrown_vessel.subtractVolume(quantity);
          }
        }

        // was a substance container, not a substance, specified as indirect object?
        if (
          target_asset.hasClass("Tangible") &&
          !target_asset.hasClass("Room") &&
          target_asset.canContainSubstance() &&
          (target_preposition === "in" ||
            target_preposition === "at" ||
            target_preposition === "to" ||
            target_preposition === "on")
        ) {
          target_preposition = target_asset.getVesselPreposition();
          target_vessel = target_asset.getVesselAt(target_preposition);
        }

        // throw substance into substance container?
        if (target_vessel) {
          // we've got mixing to do - refer to quantity
          mixer = target_vessel.addSubstance(quantity, thrown_substance.id);
          results = target_tangible.onAddSubstanceToThis(thrown_substance);
          if ("undefined" !== typeof results) return results;
        } else {
          bounce_target = currentRoom;
          bounce_preposition = currentRoom.default_aspect;
        }
      }

      // will thrown_asset land where it was thrown?
      let bounced = false;
      if (
        (thrown_asset.hasClass("Tangible") &&
          bounce_target?.id !== target_tangible.id) ||
        (thrown_asset.hasClass("Substance") &&
          !target_tangible.hasAspectAt(target_preposition))
      ) {
        bounced = true;
      }
      let try_to = bounced ? "try to" : "";

      // compose output
      msg += `$(We)`;

      // throwing substance from a substance reservoir?
      if (thrown_substance && !thrown_tangible.isWithin(player)) {
        msg += ` scoop up a handful of ${thrown_substance.name} `;
        if (!thrown_tangible.hasClass("Room")) {
          msg += ` from ${thrown_tangible.articlename}`;
        }
        msg += ` and ${try_to} ${this.name} it`;
      } else msg += ` ${try_to} ${this.name}`;

      // throwing a carried substance?
      if (
        thrown_asset.hasClass("Substance") &&
        thrown_tangible?.isWithin(player)
      ) {
        msg += ` a ${thrown_tangible.noun}-full of ${thrown_substance.name}`;
      }

      // throwing an object?
      if (thrown_asset.hasClass("Tangible")) {
        msg += ` ${thrown_asset.articlename}`;
      }

      // throwing at anything?
      if (!target_tangible.hasClass("Room")) {
        msg += `${target_preposition ? " " + target_preposition : ""}`;
        msg += ` ${target_tangible.articlename}`;
      } else {
        msg += ` across the room`;
      }

      // throwing with anything?
      msg += `${assist_preposition ? " " + assist_preposition : ""}`;
      msg += `${assist_asset ? " " + assist_asset.articlename : ""}`;

      // did it land where it was thrown? EOL
      if (bounce_target?.id === target_tangible.id) {
        msg += `. `;
      }

      // or did it land elsewhere?
      else {
        msg += `, where `;
        msg += `${thrown_asset.getPronoun("we")} `;
        msg += `${thrown_asset.hasClass("Substance") ? "spills" : "bounces"} `;
        if (bounce_target.hasClass("Room")) {
          msg += ` to the ground. `;
        } else msg += ` to ${bounce_target.articlename}. `;
      }

      // print output
      return this.handleSuccess(msg, thrown_asset);
    },
  };
})(); // throw