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

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

  /**
   * @augments {adventurejs.Verb}
   * @class fill
   * @ajsnode game.dictionary.verbs.fill
   * @ajsconstruct MyGame.createVerb({ "name": "fill", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading ManipulationVerbs
   * @summary Verb meaning fill 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; fill goblet with wine from cask</span>
   * You fill the stone goblet with foul smelling wine
   * from the goblin's cask.
   * </pre>
   * <p>
   * <strong>Fill</strong> a
   * {@link adventurejs.Tangible|Tangible}
   * {@link adventurejs.Asset|Asset} with a
   * {@link adventurejs.Substance|Substance} Asset
   * from another Tangible Asset.
   * Requires that both Tangible Assets have
   * {@link adventurejs.Vessel|Vessels}
   * and that the second actually contains the specified Substance.
   * </p>
   * <p>Substances are a unique subclass of Matter that have
   * no location, but only exist as properties in Tangible Assets,
   * ie Tangible.Aspect.Vessel.
   * Players may refer directly to a
   * substance, and we infer its container if we're able.
   * </p>
   * <p>
   * Ex: the player can input something like "fill jug with water",
   * as opposed to "fill jug from sink".
   * In such cases, instead of an object id, we get a ':' delimited
   * string in the form of tangible id : aspect id : substance id.
   * Ex: sink:in:water
   * If we did receive a triplet, we'll use that pre-parsed info
   * to get the aspect and substance.
   * If we did not receive a triplet, we'll get aspect and substance
   * via methods on the tangible object.
   * </p>
   * @ajsverbreactions
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.fill = {
    name: "fill",
    prettyname: "fill",
    past_tense: "filled",
    synonyms: ["fill"],

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

    /**
     * @memberof fill
     * @ajsverbphrase
     * phrase1:
     * {
     *   accepts_noun:true,
     *   requires_noun: true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     tangible: true,
     *     present: true,
     *     visible: true,
     *     reachable: true,
     *   },
     * },
     */
    phrase1: {
      accepts_noun: true,
      requires_noun: true,
      noun_must_be: {
        known: true,
        tangible: true,
        present: true,
        visible: true,
        reachable: true,
      },
    },

    // phrase2 can be either substance or tangible which
    // means we're not going to check present/visible/reachable
    // though we may have to in doTry
    /**
     * @memberof fill
     * @ajsverbphrase
     * phrase2:
     * {
     *   accepts_noun:true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present_if_tangible: true,
     *     // may be a substance or tangible and we
     *     // don't run these checks on substances
     *     //present: true,
     *     //visible: true,
     *     //reachable: true,
     *   },
     *   accepts_preposition:true,
     *   requires_preposition: true,
     *   accepts_these_prepositions: [ "with", "from" ],
     * },
     */
    phrase2: {
      accepts_noun: true,
      noun_must_be: {
        known: true,
        matter: true,
        present_if_tangible: true,
      },
      accepts_preposition: true,
      requires_preposition: true,
      accepts_these_prepositions: ["with", "from"],
    },

    /**
     * @memberof fill
     * @ajsverbphrase
     * phrase3:
     * {
     *   accepts_noun:true,
     *   accepts_preposition:true,
     *   noun_must_be:
     *   {
     *     known: true,
     *     matter: true,
     *     present: true,
     *     visible: true,
     *     reachable: true,
     *   },
     *   requires_preposition: true,
     *   accepts_these_prepositions: [ "from" ],
     * },
     */
    phrase3: {
      accepts_noun: true,
      accepts_preposition: true,
      noun_must_be: {
        known: true,
        matter: true,
        present: true,
        visible: true,
        reachable: true,
      },
      requires_preposition: true,
      accepts_these_prepositions: ["from"],
    },

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

    doTry: function () {
      var input = this.game.getInput();
      var target_asset = input.getAsset(1);
      var source_asset = input.getAsset(2);
      var preposition2 = input.getPreposition(2);
      var source_asset2 = input.getAsset(3);
      var preposition3 = input.getPreposition(3);
      var player = this.game.getPlayer();
      var substance_id = "";
      var containers;
      var msg = "";
      var source_volume;
      var source_vessel, target_vessel;
      var source_aspect, target_aspect;

      // player input "fill asset with substance from asset"
      if ("with" === preposition2 && "from" === preposition3) {
        // is source_asset a substance?
        substance_id = input.getParsedNoun(2).matches.substance;
        if (!substance_id) {
          this.game.debug(
            `F1559 | ${this.name}.js | ${source_asset.id} is not a substance `,
          );
          msg += `$(We) can't fill ${target_asset.articlename} with ${source_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // does source_asset2 contain same substance?
        if (!source_asset2.doesContainSubstance(substance_id)) {
          this.game.debug(
            `F1563 | ${this.name}.js | ${source_asset2.id} does not contain ${substance_id} `,
          );
          msg += `${source_asset2.articlename} doesn't contain ${substance_id}. `;
          this.handleFailure(msg);
          return null;
        }

        // we've established that source_asset2 contains substance
        // so we can simplify the input to "fill target_asset from source_asset2"
        input.setPhrase(2, input.getPhrase(3));
        input.setPhrase(3, {});
        source_asset = input.getAsset(2);
        preposition2 = input.getPreposition(2);
      } else if ("with" === preposition2) {
        // is source_asset a substance?
        substance_id = input.getParsedNoun(2).matches.substance;
        if (substance_id) {
          // if source_asset is a substance, need to find a container
          containers = this.game.findSubstanceContainers(substance_id, [
            "Present",
            "Known",
            "Visible",
            "Reachable",
          ]);
          switch (containers.length) {
            case 0:
              this.game.debug(`F1565 | ${this.name}.js | no containers found `);
              msg += `There's no ${
                this.game.getAsset(substance_id).name
              } to fill ${target_asset.articlename} with. `;
              this.handleFailure(msg);
              return null;
            case 1:
              // set input phrase2 to the container
              input.setPhrase(2, {});
              input.setPreposition(2, "from");
              input.setAsset(2, this.game.getAsset(containers[0]));
              input.setAssumed(2, true);
              source_asset = input.getAsset(2);
              preposition2 = "from";
              break;
            default:
              var asset, room;
              for (var i = 0; i < containers.length; i++) {
                asset = this.game.getAsset(containers[i]);
                if (asset.id === this.game.getCurrentRoom().id) {
                  room = true;
                  break;
                }
              }
              if (room) {
                input.setPhrase(2, {});
                input.setPreposition(2, "from");
                input.setAsset(2, asset);
                input.setAssumed(2, true);
                source_asset = input.getAsset(2);
                preposition2 = "from";
                break;
              } else {
                // disambiguate - need to set parsedNoun.matches ?
                this.game.debug(
                  `F1564 | ${this.name}.js | multiple containers found, disambiguate `,
                );
                input.setPreposition(2, "from");
                // 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
        } // if substance // not substance
        else {
          // @TODO should be able to say things like
          // "fill printer with paper"
          // source_asset is not a substance, so it must be a container
          // does source_asset contain anything to fill with?
          if (!source_asset.doesContainAnySubstance()) {
            this.game.debug(
              `F1566 | ${this.name}.js | "+${source_asset.id}+" does not contain anything `,
            );
            msg += `${source_asset.Articlename} doesn't contain anything with which to fill ${target_asset.articlename}. `;
            this.handleFailure(msg);
            return null;
          }

          // change preposition2 to "from"
          input.setPreposition(2, "from");
          preposition2 = "from";
        } // else
      } // if with
      else if ("from" === preposition2) {
        // does asset contain anything to fill with?
        if (source_asset && !source_asset.doesContainAnySubstance()) {
          this.game.debug(
            `F1560 | ${this.name}.js | ${source_asset.id} does not contain anything `,
          );
          msg += `${source_asset.Articlename} doesn't contain anything with which to fill ${target_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      }

      // can target_asset be filled?
      if (!target_asset.hasVessel()) {
        this.game.debug(
          `F1566 | ${this.name}.js | ${target_asset.id} has no substance container `,
        );
        msg += `${target_asset.Articlename} can't be filled. `;
        this.handleFailure(msg);
        return null;
      }

      // is player holding neither asset?
      if (!target_asset.isIn(player) && !source_asset.isIn(player)) {
        // If player is holding neither asset, we need
        // to consider the takeability of each asset.

        // Are both assets takeable?
        if (target_asset.isDOV("take") && source_asset.isDOV("take")) {
          this.game.debug(
            `F1567 | ${this.name}.js | neither ${target_asset.id} nor ${source_asset.id} are in player `,
          );
          msg += `$(We're) holding neither ${target_asset.articlename} nor ${source_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }

        // Are neither assets takeable?
        if (!target_asset.isDOV("take") && !source_asset.isDOV("take")) {
          this.game.debug(
            `F1569 | ${this.name}.js | both ${target_asset.id} and ${source_asset.id} are stationary `,
          );
          msg += `$(We) try transferring ${
            this.game.getAsset(source_asset.getAnySubstanceThisContains()).name
          } 
              from ${target_asset.articlename} to ${
                source_asset.articlename
              } by hand, to little effect. `;
          this.handleFailure(msg);
          return null;
        }

        // Is only one asset takeable?
        if (target_asset.isDOV("take") || source_asset.isDOV("take")) {
          var unheld = target_asset.isDOV("take") ? target_asset : source_asset;
          this.game.debug(
            `F1570 | ${this.name}.js | ${unheld.id}.dov.take.enabled is true, but is unheld `,
          );
          msg += `$(We're) not holding ${unheld.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      }

      // Is either asset closed?
      if (
        (target_aspect === "in" &&
          target_asset.isDOV("close") &&
          target_asset.is.closed) ||
        (source_aspect === "in" &&
          source_asset.isDOV("close") &&
          source_asset.is.closed)
      ) {
        var closed = target_asset.is.closed ? target_asset : source_asset;
        this.game.debug(
          `F1471 | ${this.name}.js | ${closed.id}.is.closed is true `,
        );
        msg += `${closed.Articlename} is closed. `;
        this.handleFailure(msg);
        return null;
      }

      // practically vessels are always 'in'
      // but in theory we support substances at any aspect
      target_aspect = target_asset.getAspectWithVessel();
      source_aspect = source_asset.getAspectWithVessel();
      target_vessel = target_asset.getVesselAt(target_aspect);
      source_vessel = source_asset.getVesselAt(source_aspect);

      // has source got volume?
      source_volume = source_vessel.getVolume();

      // Is source asset empty?
      if (0 >= source_volume) {
        if (source_asset instanceof adventurejs.SubstanceEmitter) {
          this.game.debug(
            `F1572 | ${this.name}.js | ${source_asset.id} is class SubstanceEmitter, but .is_emitting is false `,
          );
          msg += `Nothing is coming out of ${source_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        } else {
          this.game.debug(
            `F1573 | ${this.name}.js | ${source_asset.id}.aspects.in.vessel.volume is 0 `,
          );
          msg += `Nothing is coming out of ${source_asset.articlename}. `;
          this.handleFailure(msg);
          return null;
        }
      }

      if (
        target_vessel.getVolume() === target_vessel.maxvolume &&
        target_vessel.substance_id === source_vessel.substance_id
      ) {
        this.game.debug(
          `F1574 | ${this.name}.js | ${target_asset.id}.aspects.${target_aspect}.vessel.volume is equal to .maxvolume `,
        );
        msg += `${target_asset.Articlename} is already full of ${
          this.game.getAsset(target_vessel.substance_id).name
        }. `;
        this.handleFailure(msg);
        return null;
      }

      return true;
    },

    doSuccess: function () {
      var input = this.game.getInput();
      var target_asset = input.getAsset(1);
      var source_asset = input.getAsset(2);
      var room = source_asset instanceof adventurejs.Room;
      var msg = "";
      var results;
      var mixer;

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

      // instantiate a SubstanceMixer to handle the transfer
      mixer = new adventurejs.SubstanceMixer(this.game.game_name).set({
        can_overflow_target: false,
        source_input: source_asset.id,
        source_aspect: source_asset.getAspectWithVessel(),
        source_substance_id: source_asset.getAspectWithVessel().substance_id,
        target_input: target_asset.id,
        target_aspect: target_asset.getAspectWithVessel(),
      });
      results = mixer.mix();
      if (A.isFalseOrNull(results)) return results;
      mixer.target_vessel.vessel_is_known = true;

      msg = `$(We) fill ${mixer.target_asset.articlename} with ${
        mixer.source_substance_asset.name
      }${room ? "" : " from " + mixer.source_asset.articlename}`;
      // if source_asset is the room, don't say "from the room"

      // does the target drain?
      if (mixer.can_drain_target) {
        msg += `, but it quickly drains away`;
      }

      // was there a mixwith?
      else if (mixer.did_mix_substances) {
        msg += `, resulting in ${mixer.output_substance_asset.name}`;
      }

      // did source displace content of target?
      else if (mixer.did_displace_substance) {
        msg += `, displacing the ${
          this.game.getAsset(mixer.target_substance_id).name
        } that was already there`;
      }

      msg += `. `;

      // print output
      this.handleSuccess(msg, target_asset);
      return true;
    },
  };
})(); // fill