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

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

  /**
   * @augments {adventurejs.Verb}
   * @class drink
   * @ajsnode game.dictionary.verbs.drink
   * @ajsconstruct MyGame.createVerb({ "name": "drink", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading ConsumptionVerbs
   * @summary Verb meaning drink substance.
   * @todo finish writing
   * @tutorial Scripting_VerbSubscriptions
   * @tutorial Verbs_VerbAnatomy
   * @tutorial Verbs_VerbProcess
   * @tutorial Verbs_ModifyVerbs
   * @tutorial Verbs_WriteVerbs
   * @classdesc
   * <pre class="display border outline">
   * <span class="ajs-player-input">&gt; drink milk</span>
   * You drink milk from the carton. It does a body good.
   * </pre>
   * <p>
   * <strong>Drink</strong> a {@link adventurejs.Substance|Substance}
   * {@link adventurejs.Asset|Asset}.
   * Requires that a quantity of the Substance be present and reachable.
   * Parser will search for the specified Substance among available
   * Assets.
   * </p>
   * @ajsverbreactions doDestroy, doSubtractSubstanceFromThis, doRemoveThisFromThat, doRemoveThatFromThis
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.drink = {
    name: "drink",
    prettyname: "drink",
    past_tense: "drank",
    synonyms: ["drink", "sip"],
    gerund: "drinking",

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

    /**
     * @memberof drink
     * @ajsverbphrase
     * phrase1:
     * {
     *   accepts_noun:true,
     *   requires_noun:true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present_if_tangible: true,
     *     reachable_if_tangible: true,
     *   },
     *   accepts_preposition: true,
     *   preposition_must_be: ["from"],
     * },
     */
    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,
      },
      accepts_preposition: true,
      preposition_must_be: ["from"],
    },

    /**
     * @memberof drink
     * @ajsverbphrase
     * phrase2:
     * {
     *   accepts_noun:true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     tangible: true,
     *     present: true,
     *     visible: true,
     *     reachable: true,
     *   },
     *   accepts_preposition:true,
     *   requires_preposition: true,
     *   preposition_must_be: [ "from" ],
     * },
     */
    phrase2: {
      accepts_noun: true,
      noun_must_be: {
        known: true,
        tangible: true,
        present: true,
        visible: true,
        reachable: true,
      },
      accepts_preposition: true,
      requires_preposition: true,
      preposition_must_be: ["from"],
    },

    /**
     * @memberof drink
     * @ajsverbparams
     * with_params: {},
     */
    with_params: { on_drink_empty: false },

    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 room = this.game.getRoom();
      var containers, container;
      var msg = "";
      var results, hook;

      var find_container_for_direct_object = false;
      var find_substance_for_direct_object = false;

      // get ahead of this common situation - player inputs
      // "drink water from sink" and the sink may have a
      // running faucet and yet be technically empty and we
      // don't want to stupidly return an "empty sink" message
      // in which case make the faucet the object
      if (
        direct_object instanceof adventurejs.Substance &&
        indirect_object instanceof adventurejs.Tangible &&
        !indirect_object.containsAnySubstance() &&
        indirect_object.hasAnyPartContainingAnySubstance()
      ) {
        const substance_part =
          indirect_object.getAnyPartContainingAnySubstance();
        indirect_object = substance_part;
        input.setAsset(2, substance_part);
      }

      // has player entered "drink asset"?
      if (
        input.hasStructure("verb noun") ||
        input.hasStructure("verb preposition noun")
      ) {
        // is asset a substance? > get container
        if (direct_object instanceof adventurejs.Substance) {
          if (this.game.settings.infer_containers) {
            find_container_for_direct_object = true;
          } else {
            this.game.debug(
              `D1173 | ${this.name}.js | this.game.settings.infer_containers is false `
            );
            msg += `What would {we} like to ${this.name} ${direct_object.id} from? `;
            // soft prompt for container
            input.setPreposition(2, "from");
            input.setSoftPrompt({ index: 2, type: "noun", noun2: true });
            input.setStructure(`${input.getStructure()} preposition noun`);
            this.handleFailure(msg);
            return null;
          }
        } else {
          const substance_part =
            direct_object.getAnyPartContainingAnySubstance();

          if (direct_object.containsAnySubstance()) {
            find_substance_for_direct_object = true; // check open / reachable etc
          } else if (substance_part) {
            direct_object = substance_part;
            input.setAsset(1, substance_part);
            find_substance_for_direct_object = true; // check open / reachable etc
          } else if (
            direct_object.canContainSubstances() ||
            direct_object.canAnyPartContainSubstances()
          ) {
            this.game.debug(
              `D1575 | ${this.name}.js | ${direct_object.id} is empty `
            );
            msg += `${direct_object.Articlename_is} empty. `;
            this.handleFailure(msg);
            return null;
          } else if (direct_object.isDOV("drink")) {
            // is player carrying the thing?
            if (
              direct_object.isDOV("take") &&
              !direct_object.isWithin(subject)
            ) {
              this.game.debug(
                `D1748 | ${this.name}.js | ${direct_object.id} is not in subject `
              );
              msg += `{We're} not carrying ${direct_object.articlename}. `;
              this.handleFailure(msg);
              return null;
            }

            // special handling for things that are not substance containers
            // but which can be drunk, as in a case like "drink potion"
            // where potion asset responds directly to drink
            input.verb_params.can_be_drunk = true;
            return true;
          } else {
            this.game.debug(
              `D1171 | ${this.name}.js | ${direct_object.id}.dov.drink.enabled is false `
            );
            msg += `${direct_object.Articlename} can't be drunk from. `;
            this.handleFailure(msg);
            return null;
          }
        }
      } // verb noun

      // player input "drink substance" and we need to infer container
      if (find_container_for_direct_object) {
        containers = this.game.findSubstanceContainers(
          direct_object.id,
          this.game.getRoom(),
          ["Present", "Known", "Visible", "Reachable"]
        );

        // no containers?
        if (!containers.length) {
          this.game.debug(`D1576 | ${this.name}.js | no containers found `);
          msg += `{We} {don't} see any ${direct_object.name}. `;
          this.handleFailure(msg);
          return null;
        }

        // multiple containers?
        if (containers.length > 1) {
          console.warn({ containers });
          // are any containers being carried by subject?
          let carried_containers = [];
          for (let i = 0; i < containers.length; i++) {
            // let asset = this.game.getAsset(containers[i]);
            let asset = containers[i];
            if (asset.$is("carried")) {
              carried_containers.push(asset);
            }
          }
          if (carried_containers.length) {
            switch (carried_containers.length) {
              case 0:
                break;
              case 1:
                container = carried_containers[0];
                break;
              default:
                outer: for (let i = 0; i < carried_containers.length; i++) {
                  let carried = carried_containers[i];
                  // was this item used last turn? if so prefer that
                  let last_turn = this.game.getLastTurn();
                  for (
                    let phrase = 1;
                    phrase < last_turn.getPhraseCount();
                    phrase++
                  ) {
                    let last_turn_asset = last_turn.getAsset(phrase);
                    if (last_turn_asset.id === carried.id) {
                      container = carried;
                      break outer; // breaks out of BOTH loops
                    }
                  }
                }
                break;
            }
            // if no carried container was referred to last turn,
            // just use the first one found
            if (!container) {
              container = carried_containers[0];
            }
          } // carried_containers

          // prefer reservoir?
          if (
            !container &&
            this.game.settings.infer_containers_prefers_reservoir
          ) {
            for (var i = 0; i < containers.length; i++) {
              // let asset = this.game.getAsset(containers[i]);
              let asset = containers[i];
              if (asset.$is("reservoir") || asset.$is("emitting")) {
                container = asset;
                break;
              }
            }
          } // prefer reservoir

          // prompt if multiple?
          if (!container && !this.game.settings.auto_pick_inferred_container) {
            // disambiguate - set parsedNoun.matches for next turn
            this.game.debug(
              `D1578 | ${this.name}.js | multiple containers found, disambiguate `
            );
            // set preposition to "from" for disambiguation
            // because we're asking player to choose a container
            input.setPreposition(1, "from");
            // 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;
          } // prompt if multiple
        } // containers.length > 1

        // we found containers but none of our conditions were met?
        // use the first container found
        if (!container) {
          container = containers[0];
        }

        // set indirect object to the container
        // ie "drink water" becomes "drink water from bowl"
        if (container.id === room.id) {
          input.verb_params.drink_from_room = true;
        }
        input.setAsset(2, this.game.getAsset(container));
        input.setPreposition(2, "from");
        input.setInferred(2, true);
        input.setStructure("verb noun preposition noun");
        indirect_object = input.getAsset(2);
        indirect_preposition = input.getPreposition(2);
        this.game.printInferred(`from ${indirect_object.articlename}`);
      } // find_container_for_direct_object

      // player input "drink container" and we need to infer substance
      if (find_substance_for_direct_object) {
        indirect_object = direct_object;
        direct_object = this.game.getAsset(
          indirect_object.getAnySubstanceThisContains()
        );
        input.setAsset(1, direct_object);
        input.setPreposition(1, "");
        input.setAsset(2, indirect_object);
        input.setPreposition(2, "from");
        input.setStructure("verb noun preposition noun");
        this.game.printInferred(
          `${direct_object.name} from ${indirect_object.articlename}`
        );
      } // find_substance_for_direct_object

      // now we can get into verb noun preposition noun
      if (input.hasStructure("verb noun preposition noun")) {
        // is substance unpotable? for instance could be sand
        if (!direct_object.isDOV("drink")) {
          this.game.debug(
            `D1577 | ${this.name}.js | ${direct_object.id}.dov.drink.enabled is false `
          );
          msg += `{We} can't drink ${direct_object.name}. `;
          this.handleFailure(msg);
          return null;
        }

        const substance_part =
          indirect_object.getAnyPartContainingAnySubstance();

        // can indirect object contain substance?
        if (!indirect_object.canContainSubstances()) {
          this.game.debug(
            `D1582 | ${this.name}.js | ${indirect_object.id} has no substance container `
          );
          msg += `${indirect_object.Articlename} can't be drunk from. `;
          this.handleFailure(msg);
          return null;
        }

        // Is indirect object closed?
        if (
          indirect_object.getVesselPreposition() === "in" &&
          indirect_object.isDOV("open")
        ) {
          this.game.debug(
            `D1571 | ${this.name}.js | ${indirect_object.id}.is.closed `
          );
          msg += `${indirect_object.Articlename_is} closed. `;
          this.handleFailure(msg);
          return null;
        }
        // DOES indirect object contain any substance?
        if (!indirect_object.containsAnySubstance()) {
          if (indirect_object instanceof adventurejs.SubstanceEmitter) {
            this.game.debug(
              `D1580 | ${this.name}.js | ${indirect_object.id} is class SubstanceEmitter and .is.emitting is false `
            );
            msg += `Nothing is coming out of ${indirect_object.articlename}. `;
          } else {
            this.game.debug(
              `D1581 | ${this.name}.js | ${indirect_object.id}.aspects.in.vessel.volume is 0 `
            );
            msg += `${indirect_object.Articlename_is} empty. `;
          }
          this.handleFailure(msg);
          return null;
        }

        // does indirect object contain the specified substance?
        if (!indirect_object.containsSubstance(direct_object.id)) {
          if (indirect_object instanceof adventurejs.SubstanceEmitter) {
            this.game.debug(
              `D1339 | ${this.name}.js | ${indirect_object.id} is not emitting ${direct_object.id} `
            );
            msg += `{We} don't see any ${direct_object.name} coming out of ${indirect_object.articlename}. `;
          } else {
            this.game.debug(
              `D1340 | ${this.name}.js | ${indirect_object.id}.aspects.in.vessel.volume is 0 `
            );
            msg += `${indirect_object.Articlename_is} doesn't contain any ${direct_object.name}. `;
          }
          this.handleFailure(msg);
          return null;
        }

        // Is indirect object takeable but not in subject?
        if (
          indirect_object.isDOV("take") &&
          !indirect_object.isWithin(subject)
        ) {
          this.game.debug(
            `D1579 | ${this.name}.js | ${indirect_object.id} is not in subject `
          );
          msg += `{We're} not holding ${indirect_object.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      } // verb noun preposition noun

      return true;
    },

    doSuccess: 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 msg = "";
      var results;
      var source_aspect;
      var source_vessel;
      var substance;

      if (input.verb_params.can_be_drunk) {
        // we only arrive here if the object can be direct object of drink but
        // no substance involved. For simple things like "drink potion"

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

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

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

        results = subject.onIngestThat(direct_object);
        if ("undefined" !== typeof results) return results;

        if (direct_object && direct_object.dov.drink?.then_destroy) {
          direct_object.destroy();
        }
      } else if (input.hasStructure("verb noun")) {
        // shouldn't happen because we inferred verb noun preposition noun
      } else if (input.hasStructure("verb preposition noun")) {
        // shouldn't happen because we inferred verb noun preposition noun
      } else if (input.hasStructure("verb noun preposition noun")) {
        source_aspect = indirect_object.getVesselPreposition();
        source_vessel = indirect_object.getVesselAt(source_aspect);
        substance = this.game.getAsset(source_vessel.substance_id);

        if (indirect_object.iov.drink?.with_params.on_drink_empty) {
          source_vessel.setVolume(0);
        } else {
          source_vessel.subtractVolume(this.game.settings.mouthful);
        }

        msg += `{We} drink some ${substance.name} from ${indirect_object.articlename}`;
        if (0 >= source_vessel.getVolume()) {
          msg += `, emptying it in one gulp`;
        }
        msg = msg.trim();
        msg += `. `;

        results = subject.onIngestThat(substance);
        if ("undefined" !== typeof results) return results;
      }

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