Pre-release
AdventureJS Docs Downloads
Score: 0 Moves: 0
// pour.js also means empty
(function () {
  /*global adventurejs A*/

  /**
   * @augments {adventurejs.Verb}
   * @class pour
   * @ajsnode game.dictionary.verbs.pour
   * @ajsconstruct MyGame.createVerb({ "name": "pour", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading ManipulationVerbs
   * @summary Verb meaning pour substance from an asset.
   * @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; pour bottle into volcano</span>
   * You pour the bottle of vinegar into the papier-mâché volcano.
   * Nothing happens. As you lean in to investigate, you're disturbed
   * by a deep rumbling sound. You lean back. Nothing happens. You wait
   * for a long moment. You lean in and THE VOLCANO ERUPTS WITH THE SOULS
   * OF THE DAMNED! Wraiths stream from the sculpture, rise into the air
   * above the stone plinth, begin circling around the crowded
   * amphitheatre. The wisps spin faster and faster, raising a ghostly
   * vortex that climbs toward the darkening sky. Dammit, you knew you
   * should have used the Mentos and Coke method.
   * </pre>
   * <p>
   * <strong>Pour</strong> tries to pour the contents of the
   * given direct object. Because
   * {@link adventurejs.Aspect|Aspects} can contain both
   * {@link adventurejs.Tangible|Tangibles} and
   * {@link adventurejs.Substance|Substances}, pouring
   * means that both the Tangibles and the Substances will fall out
   * of the source {@link adventurejs.Asset|Asset}
   * and into the destination Asset. If the target is a Tangible,
   * depending on its capacity, it may not be able to hold all of the
   * contents of the source Asset. In this case, excess Tangibles and
   * Substances will overflow onto the floor.
   * </p>
   * @ajsverbreactions doAddSubstanceToThis, doSubtractSubstanceFromThis, doRemoveThisFromThat, doRemoveThatFromThis, doMoveThisToThat, doMoveThatToThis
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.pour = {
    name: "pour",
    prettyname: "pour",
    past_tense: "poured",
    synonyms: ["pour"],
    gerund: "pouring",

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

    /**
     * @memberof pour
     * @ajsverbphrase
     * phrase1:
     * {
     *   accepts_noun:true,
     *   requires_noun: true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present_if_tangible: true,
     *     reachable_if_tangible: true,
     *   },
     * },
     */
    phrase1: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        known: true,
        matter: true,
        present_if_tangible: true,
        reachable_if_tangible: true,
      },
    },

    /**
     * @memberof pour
     * @ajsverbphrase
     * phrase2:
     * {
     *   accepts_noun:true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present_if_tangible: true,
     *     reachable_if_tangible: true,
     *   },
     *   accepts_preposition:true,
     *   requires_preposition: true,
     * },
     */
    phrase2: {
      accepts_noun: true,
      noun_must_be: {
        known: true,
        matter: true,
        present_if_tangible: true,
        reachable_if_tangible: true,
      },
      accepts_preposition: true,
      requires_preposition: true,
    },

    /**
     * @memberof pour
     * @ajsverbphrase
     * phrase3:
     * {
     *   accepts_noun:true,
     *   accepts_preposition:true,
     *   requires_preposition: true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present_if_tangible: true,
     *     reachable_if_tangible: true,
     *   },
     * },
     */
    phrase3: {
      accepts_noun: true,
      accepts_preposition: true,
      requires_preposition: true,
      noun_must_be: {
        known: true,
        matter: true,
        present_if_tangible: true,
        reachable_if_tangible: true,
      },
    },

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

    doTry: function () {
      var input = this.game.getInput();
      var verb_phrase = input.verb_phrase;
      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 noun3 = input.getAsset(3);
      var preposition3 = input.getPreposition(3);
      var player = this.game.getPlayer();
      var containers, container;
      var msg = "";
      var results;

      // our goal is to get to
      // "verb noun preposition noun" aka "pour bucket into sink"
      // from whatever parsed sentence structure we've received

      // if input is "pour water"
      // or input is "pour water into sink"
      // find a container

      // if input is "pour bucket"
      // or input is "pour from bucket"
      // verify that bucket contains anything

      // if input is "pour water from bucket"
      // or input is "pour water from bucket into sink"
      // verify that bucket contains water

      // if input is "pour bucket into sink"
      // verify that bucket contains anything

      // sentence structure: verb
      if (input.hasStructure("verb")) {
      }

      // sentence structure: verb noun
      // ex: pour water / pour bucket
      if (input.hasStructure("verb noun")) {
      } // verb noun

      if (input.hasStructure("verb preposition noun")) {
      }

      // call actions
      if (input.hasStructure("verb noun preposition noun")) {
        // pour bucket into sink
      }

      // call actions
      if (input.hasStructure("verb noun preposition noun preposition noun")) {
        // pour water from bucket into sink
        // empty water from bucket into sink
      }

      if (input.hasStructure("verb preposition noun")) {
        // pour from bucket

        // is preposition one that we support?
        // for many verbs we pre-define accepted prepositions
        // and wouldn't have to ask this here but pour needs
        // to be flexible to handle either substance or tangible
        if ("from" !== direct_preposition) {
          this.game.debug(
            `D1584 | ${this.name}.js | pour ${direct_preposition} isn't currently handled `
          );
          msg += `$(We) don't know how to pour ${direct_preposition} ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // is direct object a substance?
        // usually we pre-check using NounMustBe but pour needs
        // to be flexible to handle either substance or tangible
        if (direct_object instanceof adventurejs.Substance) {
          this.game.debug(
            `D1736 | ${this.name}.js | ${direct_object.id} isn't Tangible class `
          );
          msg += `$(We) don't know how to pour from ${direct_object.name}. `;
          this.handleFailure(msg);
          return null;
        }

        // remove the "from" and treat it as "pour bucket"
        input.setPreposition(1, "");
        input.setStructure("verb noun");
      } // verb preposition noun

      // did player input "pour substance" with or without indirect object?
      if (
        direct_object instanceof adventurejs.Substance &&
        (input.hasStructure("verb noun") ||
          (input.hasStructure("verb noun preposition noun") &&
            "from" !== indirect_preposition))
      ) {
        // if so we need to look for a container in inventory
        containers = this.game.findSubstanceContainers(
          direct_object.id,
          player,
          ["InInventory"]
        );

        // no containers?
        if (!containers.length) {
          this.game.debug(`D1739 | ${this.name}.js | no containers found `);
          msg += `$(We're) not carrying anything with ${direct_object.name} in it. `;
          this.handleFailure(msg);
          return null;
        }

        // multiple containers?
        if (
          containers.length > 1 &&
          !this.game.settings.infer_containers_automatically_picks
        ) {
          // disambiguate - set parsedNoun.matches for next turn
          this.game.debug(
            `D1740 | ${this.name}.js | multiple containers found, disambiguate `
          );
          // 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;
        }

        // use the first container found
        if (!container) {
          container = containers[0];
        }

        // set direct object to the container
        // ie "pour water" becomes "pour bowl"
        input.verb_params.substance = direct_object;
        direct_object = this.game.getAsset(containers[0]);
        input.setAsset(1, direct_object);
        input.setInferred(1, true);
        this.game.printInferred(`from ${direct_object.articlename}`);
      } // find substance container

      if (input.hasStructure("verb noun")) {
        // pour water / pour bucket

        if (direct_object instanceof adventurejs.Tangible) {
          // does direct object contain any substance?
          if (!direct_object.containsAnySubstance()) {
            this.game.debug(
              `D1737 | ${this.name}.js | ${direct_object.id} doesn't contain substance `
            );
            msg += `${direct_object.Articlename} doesn't contain anything pourable. `;
            this.handleFailure(msg);
            return null;
          }

          // find target - player parent / room
          indirect_object = player.getNestOrPlaceAsset();
          indirect_preposition = player.getNestOrPlacePreposition();
          input.setAsset(2, indirect_object);
          input.setPreposition(2, indirect_preposition);
          input.setInferred(2, true);
          input.setStructure("verb noun preposition noun");
          this.game.printInferred(
            indirect_object.hasClass("Room")
              ? `on the floor`
              : `${indirect_preposition} ${indirect_object.articlename}`
          );
        }
      } // verb noun

      if (input.hasStructure("verb noun preposition noun preposition noun")) {
        // pour water from bucket into sink

        // "from" is the only preposition we accept
        // for many verbs we already would have pre-checked this
        // but pour needs to be flexible to accommodate several syntax forms
        if ("from" !== indirect_preposition) {
          this.game.debug(
            `D1737 | ${this.name}.js | pour ${indirect_preposition} isn't currently handled `
          );
          msg += `$(We) don't know how to pour ${indirect_preposition} ${indirect_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // is direct object a substance? that's the only class we handle in this form
        if (!(direct_object instanceof adventurejs.Substance)) {
          this.game.debug(
            `D1738 | ${this.name}.js | ${direct_object.id} isn't Substance class `
          );
          msg += `$(We) don't know how to pour ${direct_object.name}. `;
          this.handleFailure(msg);
          return null;
        }

        // does indirect object contain direct object?
        if (!indirect_object.containsSubstance(direct_object.id)) {
          this.game.debug(
            `D1557 | ${this.name}.js | ${indirect_object.id} does not contain ${direct_object.id} `
          );
          msg += `${indirect_object.Articlename} doesn't contain ${direct_object.name}. `;
          this.handleFailure(msg);
          return null;
        }

        // did player input "pour substance from A to B"?
        // that's a reasonable phrase, but 'to' isn't a
        // normal aspect so change it to default aspect
        if ("to" === preposition3) {
          preposition3 = noun3.default_aspect;
          input.setPreposition(3, preposition3);
        }

        // though player explicitly named substance as direct object,
        // now that we've confirmed its presence in indirect object,
        // we're removing it from the input to streamline handling
        input.verb_params.substance = direct_object;
        input.setPhrase(1, input.getPhrase(2));
        direct_object = input.getAsset(1);
        direct_preposition = input.getPreposition(1);
        input.setPhrase(2, input.getPhrase(3));
        indirect_object = input.getAsset(2);
        indirect_preposition = input.getPreposition(2);
        input.setPhrase(3, {});
        input.setStructure("verb noun preposition noun");
      }

      if (input.hasStructure("verb noun preposition noun")) {
        // pour bucket into sink

        // we've already checked for "pour water into sink"
        // and replaced water with bucket

        // is direct_object in player?
        if (!direct_object.isWithin(player)) {
          this.game.debug(
            `D1552 | ${this.name}.js | ${direct_object.id}.place not in player `
          );
          msg += `$(We're) not carrying ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // does direct_object have vessel?
        if (!direct_object.hasVessel()) {
          this.game.debug(
            `D1271 | ${this.name}.js | ${direct_object.id} has no aspect with vessel `
          );
          msg += `$(We) can't pour ${direct_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // does target have aspect?
        if (
          !indirect_object.hasAspectAt(indirect_preposition) &&
          "on" !== indirect_preposition
        ) {
          // took away a check for floor here as in "pour bucket on floor"
          // but we're going to allow "pour on" anything regardless of aspects
          this.game.debug(
            `D1550 | ${this.name}.js | ${indirect_object.id} does not have ${indirect_preposition} aspect `
          );
          msg += `$(We) can't pour ${direct_object.articlename} ${indirect_preposition} ${indirect_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // is direct_object closed?
        if (direct_object.isDOV("open")) {
          this.game.debug(
            `D1272 | ${this.name}.js | ${direct_object.id}.is.closed `
          );
          msg += `${direct_object.Articlename_is} closed. `;
          this.handleFailure(msg);
          return null;
        }

        // is direct_object empty?
        if (!direct_object.containsAnySubstance()) {
          this.game.debug(
            `D1551 | ${this.name}.js | ${direct_object.id}.in.vessel.getVolume is 0 `
          );
          msg += `${direct_object.Articlename_is} empty. `;
          this.handleFailure(msg);
          return null;
        }

        // is target closed?
        if ("in" === indirect_preposition && indirect_object.isDOV("open")) {
          this.game.debug(
            `D1554 | ${this.name}.js | ${indirect_object.id}.is.closed `
          );
          msg += `${indirect_object.Articlename_is} closed. `;
          this.handleFailure(msg);
          return null;
        }

        // is target the floor? set to room
        if (indirect_object instanceof adventurejs.Floor) {
          indirect_object = this.game.getCurrentRoom();
          input.setAsset(2, indirect_object);
        }
      }

      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var verb_phrase = input.verb_phrase;
      var direct_object = input.getAsset(1);
      var indirect_object = input.getAsset(2);
      var indirect_preposition = input.getPreposition(2);
      var player = this.game.getPlayer();
      var msg = "";
      var results;
      var room = indirect_object instanceof adventurejs.Room;
      var target_object, target_preposition;
      var emptied_count = 0;
      var transferred = [];
      var spillover = [];
      var overflow = false;
      var substance_asset;
      var empty_this = false;
      var mixer;

      // sentence structure: verb
      if (input.hasStructure("verb")) {
      }

      // sentence structure: verb noun
      if (input.hasStructure("verb noun")) {
      } // verb noun

      // all parsed sentence structures should have resolved into
      // "verb noun preposition noun" because we always need
      // a source and a target

      if (indirect_object) {
      }

      msg += `$(We) tip ${direct_object.articlename}`;
      if (
        "under" === indirect_preposition ||
        "behind" === indirect_preposition
      ) {
        msg += ` ${indirect_preposition} ${indirect_object.articlename}`;
      } else {
        msg += ` over ${room ? "the floor" : indirect_object.articlename}`;
      }
      msg += `. `;

      // handle substances
      if (direct_object.hasVesselAtAspect("in")) {
        if (0 < direct_object.aspects.in.vessel.getVolume()) {
          // direct_object has substance
          substance_asset = this.game.getAsset(
            direct_object.aspects.in.vessel.substance_id
          );

          if (
            room ||
            "under" === indirect_preposition ||
            "behind" === indirect_preposition
          ) {
            // if indirect_object is room, substance just spills out
            msg += `${substance_asset.Name} spills to the floor. `;
            empty_this = true;
          } else if (
            "on" === indirect_preposition &&
            !indirect_object.hasVesselAtAspect(indirect_preposition)
          ) {
            // player said "pour on" and target has no on aspect
            msg += `${substance_asset.Name} pours over ${indirect_object.articlename} and then drains away. `;
            empty_this = true;
          } else if (indirect_object.hasVesselAtAspect(indirect_preposition)) {
            mixer = new adventurejs.SubstanceMixer(this.game.game_name).set({
              source_input: direct_object.id,
              source_aspect: "in",
              source_substance_id: direct_object.aspects.in.vessel.substance_id,
              target_input: indirect_object.id,
              target_aspect: indirect_preposition,
            });
            results = mixer.mix();
            if (A.isFalseOrNull(results)) return results;
            mixer.target_vessel.known = true;

            msg += `${mixer.source_substance_asset.Name} 
              pours from 
              ${mixer.source_asset.articlename} 
              ${indirect_preposition} 
              ${
                indirect_preposition === "in" || indirect_preposition === "on"
                  ? "to "
                  : ""
              }
              ${mixer.target_asset.articlename}`;

            if (mixer.can_drain_target) {
              msg += `, where it quickly drains away`;
            } else if (mixer.did_overflow_target) {
              msg += `, overflowing ${mixer.target_asset.articlename} with {mixer.did_mix_substances ? mixer.output_substance_asset.name : mixer.source_substance_asset.name}`;
            } else if (mixer.did_fill_target) {
              msg += `, filling ${mixer.target_asset.articlename} with ${
                mixer.did_mix_substances
                  ? mixer.output_substance_asset.name
                  : mixer.source_substance_asset.name
              }`;
            } else if (mixer.did_mix_substances) {
              msg += `, resulting in ${mixer.output_substance_asset.name}`;
            }
            msg += `. `;
          } else {
            msg += `${substance_asset.Name} pours over ${indirect_object.articlename} and then spills to the floor. `;
            empty_this = true;
          }
        }

        if (
          empty_this &&
          isFinite(direct_object.aspects.in.vessel.getVolume())
        ) {
          direct_object.aspects.in.vessel.setVolume(0);
        }
      } // handle substances

      // handle physical assets
      if (0 < direct_object.aspects.in.contents.length) {
        for (
          var i = direct_object.aspects.in.contents.length - 1;
          i > -1;
          i--
        ) {
          var content_object = this.game.getAsset(
            direct_object.aspects.in.contents[i]
          );
          target_object = indirect_object;
          target_preposition = indirect_preposition;
          overflow = false;
          emptied_count++;

          // if indirect_object is not the room, we need to check
          // if it can hold each object
          // if it can't, objects should fall to the floor

          if (room) {
            transferred.push(content_object);
          } else {
            if (
              indirect_object.canContainAssetAt(
                content_object,
                indirect_preposition
              )
            ) {
              transferred.push(content_object);
            } else {
              target_object = this.game.getCurrentRoom();
              target_preposition = "in";
              spillover.push(content_object);
              overflow = true;
            }
          }

          // remove content_object from direct_object
          results = direct_object.onRemoveThatFromThis(content_object);
          if ("undefined" !== typeof results) return results;

          // set content_object's target_object
          // if no target_object was specified, set to player's parent
          results = target_object.onMoveThatToThis(
            content_object,
            target_preposition
          );
          if ("undefined" !== typeof results) return results;

          // msg += "and you succeed. ";
        }
      }

      // print the results of physical assets
      if (room) {
        // everything falls to the floor
        switch (emptied_count) {
          case 0:
            break;
          case 1:
            msg += "A single item tumbles out and falls to the floor. ";
            break;
          case 2:
            msg += "Two items tumble out and fall to the floor. ";
            break;
          case 3:
            msg += "Several items tumble out and fall to the floor. ";
            break;
          default:
            msg += "A number of items tumble out and fall to the floor. ";
            break;
        }
      } else {
        var to_target =
          indirect_preposition +
          (indirect_preposition === "in" || indirect_preposition === "on"
            ? " to "
            : " ") +
          indirect_object.articlename;

        switch (emptied_count) {
          // some items are transferred to target, some may fall to the floor
          case 0:
            break;
          case 1:
            msg += "A single item tumbles " + to_target;
            switch (spillover.length) {
              case 0:
                msg += ". ";
                break;
              case 1:
                msg += " then falls to the floor. ";
                break;
            }
            break;
          case 2:
            msg += "Two items tumble " + to_target;
            switch (spillover.length) {
              case 0:
                msg += ". ";
                break;
              case 1:
                msg += ", and one falls to the floor. ";
                break;
              case 2:
                msg += " then fall to the floor. ";
                break;
            }
            break;
          case 3:
            msg += "Several items tumble " + to_target;
            switch (spillover.length) {
              case 0:
                msg += ". ";
                break;
              case 1:
                msg += ", and one falls to the floor. ";
                break;
              case 2:
                msg += ", and a couple of them fall to the floor. ";
                break;
              case 3:
                msg += " then fall to the floor. ";
                break;
            }
            break;
          default:
            msg += "A number of items tumble " + to_target;
            switch (spillover.length) {
              case 0:
                msg += ". ";
                break;
              case 1:
                msg += ", and one falls to the floor. ";
                break;
              case 2:
                msg += ", and a couple of them fall to the floor. ";
                break;
              case 3:
                msg += ", and several fall to the floor. ";
                break;
              default:
                msg += " and some fall to the floor. ";
                break;
            }
            break;
        }
      }

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