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 Verbs_Subscriptions
   * @tutorial AdvancedVerbs_VerbAnatomy
   * @tutorial AdvancedVerbs_VerbProcess
   * @tutorial AdvancedVerbs_ModifyVerbs
   * @tutorial AdvancedVerbs_ModifyVerbs
   * @classdesc
   * <pre class="display border outline">
   * <span class="ajs-player-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,
        prefer_carried_if_ambiguous: 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 subject = input.getSubject();
      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 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,
          subject,
          ["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.auto_pick_inferred_container
        ) {
          // 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() &&
            direct_object.hasAnyPartContainingAnySubstance()
          ) {
            direct_object = direct_object.getAnyPartContainingAnySubstance();
            input.setAsset(1, direct_object);
          }
          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 - subject parent / room
          indirect_object = subject.getNestOrPlaceAsset();
          indirect_preposition = subject.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 subject?
        if (!direct_object.isWithin(subject)) {
          this.game.debug(
            `D1552 | ${this.name}.js | ${direct_object.id}.place not in subject `
          );
          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.getRoom();
          input.setAsset(2, indirect_object);
        }
      }

      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var subject = input.getSubject();
      var direct_object = input.getAsset(1);
      var indirect_object = input.getAsset(2);
      var indirect_preposition = input.getPreposition(2);
      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 = msg.trim();
      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.is.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 = msg.trim();
            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.getRoom();
              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 subject'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);
    },
  };
})(); // pour