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

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

  /**
   * @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}. Requires that the Asset is
   * in player's inventory.
   * </p>
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.throw = {
    name: "throw",
    prettyname: "throw",
    past_tense: "threw",
    synonyms: ["throw", "toss"],

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

    player_must_be: {
      not_constrained: true,
    },

    /**
     * @memberof throw
     * @ajsverbphrase
     * phrase1:
     * {
     *   accepts_noun:true,
     *   requires_noun: true,
     *   noun_must_be:
     *   {
     *     //in_hands: true, // defer this check until after substance check
     *     matter: true,
     *     present_if_tangible: true,
     *   },
     * },
     */
    phrase1: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        matter: true,
        present_if_tangible: true,
      },
    },

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

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

    doTry: function () {
      var input = this.game.getInput();
      var direct_object = input.getAsset(1);
      var indirect_object = input.getAsset(2);
      var indirect_preposition = input.getPreposition(2);
      var player = this.game.getPlayer();
      var currentRoom = this.game.getCurrentRoom();
      var msg = "";
      var containers;
      var bodycontainers;
      var results;
      var inhands;
      var direct_object_vessel;
      var indirect_object_vessel;

      // did player input something like "throw sand" ?
      // if so it must come from a substance container that
      // is either in hands, or in a reachable substance body
      if (direct_object instanceof adventurejs.Substance) {
        // get a list of available vessels
        // this includes only things in inventory and nearby bodies of substance

        containers = this.game.findSubstanceBodyOrHeld(direct_object.id);

        // console.warn("containers", containers);

        switch (containers.length) {
          case 0:
            this.game.debug(`F1447 | ${this.name}.js | no valid vessel found `);
            msg += `There doesn't appear to be any ${
              this.game.getAsset(direct_object.id).name
            } to throw. `;
            this.handleFailure(msg);
            return null;
          case 1:
            // set container as direct_object
            // and treat like "throw asset in container"
            direct_object_vessel = this.game.getAsset(containers[0]);
            input.setAssumed(1, true);
            input.setVessel(1, direct_object_vessel);
            break;
          default:
            this.game.debug(
              `F1448 | ${this.name}.js | multiple containers found, disambiguate `
            );
            // disambiguate - need to set parsedNoun.matches
            // save containers back to input for next turn disambiguation
            input.setParsedNounMatchesQualified(1, containers);
            this.game.parser.printNounDisambiguation({
              parsedNoun: input.getParsedNoun(1),
              nounIndex: 1,
            });
            return null;
        } // switch containers.length
      } // direct_object is substance // not substance
      else {
        // we bypassed the usual in_hands check to allow
        // substance handling so we need to check that now
        inhands = this.game.parser.selectInHands(direct_object.id);
        if (!inhands.length) {
          this.game.debug(
            `F1601 | ${this.name}.js | ${direct_object.id} not in player's hands `
          );
          msg += `$(We're) not holding ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      }

      if (input.hasStructure("verb noun preposition")) {
        if (["up", "down"].indexOf(indirect_preposition) > -1) {
          input.deletePhrase(2);
          input.setStructure("verb noun");
          indirect_preposition = false;
        } else {
          this.game.debug(
            `F1956 | ${this.name}.js | no handling for ${this.name} ${direct_object.id} ${indirect_preposition} `
          );
          msg += `$(We) don't know how to ${this.name} ${direct_object.id} ${indirect_preposition}. `;
          this.handleFailure(msg);
          return null;
        }
      }

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

      if (indirect_object) {
        if (indirect_object.isIn(player)) {
          this.game.debug(
            `F1612 | ${this.name}.js | ${indirect_object.id}.isIn player `
          );
          msg += `$(We're) carrying ${indirect_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // is indirect_object a substance?
        // if so find a container
        if (indirect_object instanceof adventurejs.Substance) {
          // get a list of available
          containers = this.game.findSubstanceContainers(indirect_object.id, [
            "Present",
            "Known",
            "Visible",
          ]);

          if (this.game.settings.throw_at_substance_prefers_bodies) {
            // if only bodies of substance will do, use this
            containers = this.game.parser.selectBodyOfSubstance(containers);
          } else {
            // if any vessel will do, use this
            // presumes player is not trying to throw at a vessel they're holding
            containers = this.game.parser.selectNotInInventory(containers);
          }

          switch (containers.length) {
            case 0:
              this.game.debug(
                `F1444 | ${this.name}.js | no body of substance found `
              );
              msg += `There doesn't appear to be any ${
                this.game.getAsset(indirect_object.id).name
              } suitable for throwing ${indirect_preposition}. `;
              this.handleFailure(msg);
              return null;
            case 1:
              // save the found vessel back to the input object
              indirect_object_vessel = this.game.getAsset(containers[0]);
              input.setAssumed(2, true);
              input.setVessel(2, indirect_object_vessel);
              break;
            default:
              this.game.debug(
                `F1445 | ${this.name}.js | multiple containers found, disambiguate `
              );
              // disambiguate - need to set parsedNoun.matches
              // save containers back to input for next turn disambiguation
              input.setParsedNounMatchesQualified(2, containers);
              this.game.parser.printNounDisambiguation({
                parsedNoun: input.getParsedNoun(2),
                nounIndex: 2,
              });
              return null;
          } // switch containers.length
        } // substance
      } // indirect_object

      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var direct_object = input.getAsset(1);
      var indirect_object = input.getAsset(2);
      var indirect_preposition = input.getPreposition(2);
      var currentRoom = this.game.getCurrentRoom();
      var player = this.game.getPlayer();
      var msg = "";
      var direct_object_vessel = this.game.getAsset(input.getVessel(1));
      var indirect_object_vessel = this.game.getAsset(input.getVessel(2));
      var dv, iv;
      var results;
      var can_contain;
      var input_verb = input.input_verb;

      this.game.debug(`F1449 | ${this.name}.js | print doSuccess`);

      if (direct_object_vessel) {
        dv = direct_object_vessel.getVesselAt(
          direct_object_vessel.getAspectWithVessel()
        );
      }
      if (indirect_object_vessel) {
        iv = indirect_object_vessel.getVesselAt(
          indirect_object_vessel.getAspectWithVessel()
        );
      }

      // if thrown thing isn't substance, remove it from player
      if (!(direct_object instanceof adventurejs.Substance)) {
        // remove thing from player
        results = player.onRemoveThatFromThis(direct_object);
        if ("undefined" !== typeof results) return results;
      }

      // did player input "throw asset" without a target?
      if (!indirect_object) {
        // if player input something like "throw sand"
        // then we chose a vessel for them that contains sand
        // either in inventory or a body of substance
        // and we're going to throw the sand, not the vessel
        if (direct_object instanceof adventurejs.Substance) {
          this.game.debug(
            `F1602 | ${this.name}.js | no indirect object and direct object is substance `
          );
          msg += `$(We) sprinkle a handful of ${direct_object.name}`;
          if (direct_object_vessel && !direct_object_vessel.$is("body")) {
            msg += ` from ${direct_object_vessel.articlename}`;
          }
          msg += ` into the air. `;
          if (dv.volume !== Infinity) {
            dv.subtractVolume(this.game.settings.handful);
          }
        } // substance // not substance
        else {
          this.game.debug(`F1603 | ${this.name}.js | no indirect object `);
          msg += `$(We) ${input_verb} ${direct_object.articlename}. `;
          msg += direct_object.getDescription("throw");

          // @TODO add logic for water rooms, anti-gravity

          // set thing's new location to player's location
          results = currentRoom.onMoveThatToThis(direct_object, "in");
          if ("undefined" !== typeof results) return results;
        } // not substance
      } // no indirect_object

      if (indirect_object) {
        if (
          direct_object instanceof adventurejs.Substance &&
          indirect_object instanceof adventurejs.Substance
        ) {
          // we're throwing substance from direct_object_vessel
          // to indirect_object_vessel
          // our target must be a body of substance so there's
          // not going to be any mixing
          // but we can empty the direct_object_vessel

          if (direct_object_vessel.isIn(player)) {
            this.game.debug(
              `F1604 | ${this.name}.js | both objects are substances and player is holding substance vessel `
            );
            msg += `$(We) ${input_verb} ${direct_object.articlename} from ${direct_object_vessel.articlename} ${indirect_preposition} ${indirect_object.articlename}. `;
            dv.empty();
          } // from body to body
          else {
            this.game.debug(
              `F1605 | ${this.name}.js | both objects are substance bodies `
            );
            msg += `$(We) scoop a handful of ${direct_object.articlename} ${indirect_preposition} ${indirect_object.articlename}. `;
          }
        } // both substances
        else if (direct_object instanceof adventurejs.Substance) {
          // we're throwing substance at asset
          if (direct_object_vessel.isIn(player)) {
            this.game.debug(
              `F1606 | ${this.name}.js | direct object is substance held by player `
            );
            msg += `$(We) ${input_verb} ${direct_object.articlename} from ${direct_object_vessel.articlename} ${indirect_preposition} ${indirect_object.articlename}. `;
            dv.empty();
          } // from body to asset
          else {
            this.game.debug(
              `F1608 | ${this.name}.js | direct object is substance in a body `
            );
            msg += `$(We) scoop a handful of ${direct_object.articlename} ${indirect_preposition} ${indirect_object.articlename}. `;
          }
        } // direct is substance
        else if (indirect_object instanceof adventurejs.Substance) {
          // we're throwing asset at substance
          this.game.debug(
            `F1609 | ${this.name}.js | indirect object is substance in a body `
          );
          msg += `$(We) ${input_verb} ${direct_object.articlename} ${indirect_preposition} ${indirect_object.articlename}. `;

          // set thing's new location to vessel
          // this might need more consideration for prepositions
          // ie "throw asset over water" does it land on other side?
          // though hopefully the hooks give enough opportunity for customization
          // also might want to consider whether this asset can be placed in this vessel
          // and whether throwing something in it means destroying the asset
          results = indirect_object_vessel.onMoveThatToThis(
            direct_object,
            "in"
          );
          if ("undefined" !== typeof results) return results;
        } // indirect is substance // two assets, neither substance
        else {
          // we're throwing asset at asset
          // check preposition against iobj aspects
          if (
            ["in", "on", "under", "behind"].indexOf(indirect_preposition) > -1
          ) {
            if (
              indirect_object.canContainAssetAt(
                direct_object,
                indirect_preposition
              )
            ) {
              can_contain = true;
            }
          }

          if (can_contain) {
            // if asset lands in other asset
            results = indirect_object.onMoveThatToThis(
              direct_object,
              indirect_preposition
            );
            this.game.debug(
              `F1610 | ${this.name}.js | both objects are tangible, indirect can contain direct `
            );
            msg += `$(We) ${input_verb} ${direct_object.articlename} neatly ${indirect_preposition} ${indirect_object.articlename}. `;
          } else {
            // if asset lands in room
            results = currentRoom.onMoveThatToThis(direct_object, "in");
            this.game.debug(
              `F1611 | ${this.name}.js | both objects are tangible, direct lands in room `
            );
            msg += `$(We) ${input_verb} ${direct_object.articlename} ${indirect_preposition} ${indirect_object.articlename}. It falls to the floor. `;
          }
          if ("undefined" !== typeof results) return results;
        }
      } // indirect object

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