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

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

  /**
   * @augments {adventurejs.Verb}
   * @class examine
   * @ajsnode game.dictionary.verbs.examine
   * @ajsconstruct MyGame.createVerb({ "name": "examine", [...] });
   * @ajsconstructedby adventurejs.Dictionary#createVerb
   * @hideconstructor
   * @ajsinstanceof Verb
   * @ajsnavheading SensationVerbs
   * @summary Verb meaning examine 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; examine foot</span>
   * You examine the elephant's foot. Among the numerous black
   * umbrellas parked in it, one yellow umbrella stands out.
   * </pre>
   * <p>
   * <strong>Examine</strong> an {@link adventurejs.Asset|Asset}
   * returns a description of the Asset.
   * </p>
   * @ajsverbphases doBeforeTry, doAfterTry, doBeforeSuccess, doAfterSuccess
   */
  A.Preverbs.examine = {
    name: "examine",
    prettyname: "examine",
    past_tense: "examined",
    synonyms: ["examine", "x"],
    verb_prep_noun: ["carefully examine"],
    verb_noun_prep_noun: ["examine carefully"],

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

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

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

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

    msgNoObject: "What did you want to examine?",

    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 currentRoom = this.game.getCurrentRoom();
      var player = this.game.getPlayer();
      var msg = "";
      var available;
      var containers;
      var dPlace;
      var dPlacePrep;

      // is direct object a substance?
      // we have a fully different set of rules for examining substances
      if (direct_object instanceof adventurejs.Substance) {
        //if( input.getParsedNoun(1).matches.substance )
        // has user input "examine substance" vs "examine substance in container"?
        if (indirect_object) {
          // is indirect_object also a substance?
          // @TODO there might be situations where this makes sense
          // such as a body of water with sand at its bottom "examine sand in water"
          // or a colloidal suspension - but would that be a new substance?
          if (indirect_object instanceof adventurejs.Substance) {
            this.game.debug(
              `F1589 | ${this.name}.js | ${direct_object.id} and ${indirect_object.id} are both substances `
            );
            msg += `$(We) can't examine "${direct_object.name} in ${indirect_object.name}. `;
            this.handleFailure(msg);
            return null;
          }

          // in order to allow examining substances, we skipped our usual
          // check for "present" and "visible" in the parser
          // so we need to do it here for indirect_object
          available = this.game.parser.selectPresent(indirect_object.id);
          available = this.game.parser.selectVisible(available);

          if (available.length === 0) {
            this.game.debug(
              `F1588 | ${this.name}.js | ${indirect_object.id} is either unknown or not present or not visible `
            );
            msg += `${indirect_object.Articlename} doesn't contain any ${direct_object.name}. `;
            this.handleFailure(msg);
            return null;
          }

          // does indirect_object contain substance?
          if (!indirect_object.doesContainSubstance(direct_object.id)) {
            // does it contain some other substance?
            if (indirect_object.doesContainAnySubstance()) {
              this.game.debug(
                `F1595 | ${this.name}.js | ${
                  indirect_object.id
                }.doesContainAnySubstance is ${indirect_object.getAnySubstanceThisContains()}`
              );
              msg += `${indirect_object.Articlename} contains ${
                this.game.getAsset(
                  indirect_object.getAnySubstanceThisContains()
                ).name
              }. `;
              this.handleFailure(msg);
              return null;
            }

            this.game.debug(
              `F1587 | ${this.name}.js | ${indirect_object.id}.doesContainSubstance ${direct_object.id} is false `
            );
            msg += `${indirect_object.Articlename} doesn't contain any ${direct_object.name}. `;
            this.handleFailure(msg);
            return null;
          }
        } // indirect_object
        else if (!indirect_object) {
          // user has input "examine substance"
          // is any substance present?
          // get a list of available
          containers = this.game.findSubstanceContainers(direct_object.id, [
            "Present",
            "Known",
            "Visible",
          ]);

          switch (containers.length) {
            case 0:
              this.game.debug(`F1585 | ${this.name}.js | no containers found `);
              msg += `There's no ${
                this.game.getAsset(direct_object.id).name
              } available. `;
              this.handleFailure(msg);
              return null;
            case 1:
              // set container as indirect_object
              // and treat like "examine water in bowl"
              input.setPreposition(2, "in");
              input.setAsset(2, this.game.getAsset(containers[0]));
              input.setAssumed(2, true);
              indirect_object = input.getAsset(2);
              indirect_preposition = input.getPreposition(2);
              break;
            default:
              var asset, body;
              for (var i = 0; i < containers.length; i++) {
                asset = this.game.getAsset(containers[i]);
                //if( asset.id === currentRoom.id )
                if (asset.$is("body")) {
                  body = true;
                  break;
                }
              }
              // if body is among multiple sources, always prefer body
              // meant for situations like player is on a beach,
              // where sand is always available
              if (body) {
                // set input phrase1 to the body
                // ie "examine sand" becomes "examine sand in beach"
                // doSuccess will handle the rest
                input.setPreposition(2, "in");
                input.setAsset(2, asset);
                input.setAssumed(2, true);
                indirect_object = input.getAsset(2);
                break;
              } // body
              else if (!body) {
                // disambiguate - need to set parsedNoun.matches ?
                this.game.print(
                  `F1586 | ${this.name}.js | multiple containers found, disambiguate `
                );
                // set preposition to "from" for disambiguation
                // because we're asking player to choose a container
                input.setAsset(2, direct_object);
                input.setPreposition(2, "in");
                // 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;
              } // !body
          } // switch
        } // !indirect_object

        // if we get here, we have a substance and a container
        return true;
      } // substance

      // NOT A SUBSTANCE

      // is direct_object present and visible?
      available = this.game.parser.selectPresent(direct_object.id);
      available = this.game.parser.selectVisible(available);

      if (!available.length) {
        this.game.debug(
          `F1591 | ${this.name}.js | ${direct_object.id} either is not present or not visible `
        );
        msg += `$(We) don't see any ${direct_object.name}. `;
        this.handleFailure(msg);
        return null;
      }

      // has user input "examine object in substance"?
      // can imagine situations like "examine boat in water"
      if (input.hasStructure("verb noun preposition noun")) {
        if (indirect_object instanceof adventurejs.Substance) {
          // need to find a container that contains indirect_object
          // and also has direct_object in it
          // may just be able to get direct_object's parent
          // and see if that contains Substance
          dPlace = this.game.getAsset(direct_object.getPlaceAssetId());
          dPlacePrep = direct_object.getPlacePreposition();
          // if( !dPlace.doesContainSubstance(indirect_object.id) )
          if (dPlace.getSubstanceAt(dPlacePrep) !== indirect_object.id) {
            this.game.debug(
              `F1590 | ${this.name}.js | ${direct_object.id}.place is ${dPlace.id} which doesn't contain ${indirect_object.id} `
            );
            msg += `${direct_object.Articlename} doesn't appear to be in  ${indirect_object.name}. `;
            this.handleFailure(msg);
            return null;
          }
        } // indirect_object is Substance
        else if (!(indirect_object instanceof adventurejs.Substance)) {
          // is indirect_object present and visible?
          available = this.game.parser.selectPresent(indirect_object.id);
          available = this.game.parser.selectVisible(available);

          if (available.length === 0) {
            this.game.debug(
              `F1592 | ${this.name}.js | ${indirect_object.id} either is not present or not visible `
            );
            msg += `$(We) don't see any ${indirect_object.name}. `;
            this.handleFailure(msg);
            return null;
          }

          // is direct_object in indirect_object?
          if (
            indirect_preposition !== direct_object.getPlacePreposition() ||
            indirect_object.id !== direct_object.getPlaceAssetId()
          ) {
            // did player input "on" but thing is attached? allow it
            if (
              indirect_preposition === "on" &&
              direct_object.getPlacePreposition() === "attached"
            ) {
              // allowed - do nothing
            } else if (
              indirect_preposition === "in" &&
              direct_object.getPlacePreposition() === "on" &&
              indirect_object.quirks.in_means_on
            ) {
              // allowed - do nothing
            } else if (
              direct_object.hasIndirectDescription(
                "look",
                indirect_preposition,
                indirect_object
              )
            ) {
              // allowed - do nothing
              input.verb_params.hasIndirectDescription = true;
            } else {
              this.game.debug(
                `F1593 | ${this.name}.js | ${direct_object.id} is not ${indirect_preposition} ${indirect_object.id} `
              );
              msg += `${direct_object.Articlename} doesn't appear to be ${indirect_preposition} ${indirect_object.articlename}. `;
              this.handleFailure(msg);
              return null;
            }
          }
        } // indirect_object is not Substance
      } // verb noun preposition noun

      return true;
    }, // doTry

    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 room = indirect_object && indirect_object.id === currentRoom.id;
      var careful = "";

      if (input.verb_params.hasIndirectDescription) {
        this.game.debug(
          `F1844 | ${this.name}.js | doSuccess >  ${direct_object.id} ${indirect_preposition} ${indirect_object.id} `
        );
        msg += direct_object.getIndirectDescription(
          "look",
          indirect_preposition,
          indirect_object
        );
        this.handleSuccess(msg, direct_object);
        return true;
      }

      if (direct_object instanceof adventurejs.Substance) {
        // indirect_object will be set regardless if one was entered
        this.game.debug(
          `F1594 | ${this.name}.js | doSuccess >  ${direct_object.id} ${indirect_preposition} ${indirect_object.id} `
        );
        msg += `$(We) look at ${direct_object.articlename}`;
        msg += !room ? ` in ${indirect_object.articlename}` : ``;
        msg += `. `;

        msg += direct_object.getDescription("look");
        this.handleSuccess(msg, direct_object);
        return true;
      }

      if (direct_object.id === currentRoom.id) {
        this.game.printCurrentRoom();
        return true;
      } else if (direct_object.is.global) {
        /**
         * direct_object.is.global applies to global
         * floor, ceiling, walls, sun & sky, etc
         * See Game.getGlobalAssetDescription
         * for more info.
         */
        let global_description =
          this.game.getGlobalAssetDescription(direct_object);
        if (global_description) {
          msg = A.getSAF.call(this.game, global_description);
        }
      } // is.global
      else if (
        input.parsedNoun1.matches.direction ||
        direct_object instanceof adventurejs.Exit
      ) {
        var exit = direct_object;

        if ("undefined" === typeof exit) {
          msg += `$(We) don't see any exit in that direction. `;
          this.handleFailure(msg);
          return true;
        }

        var exitDescription = exit.getDescription("look");
        if (exitDescription) {
          // if the exit has a description, print that
          msg += exitDescription;
        } else {
          // otherwise print this
          msg += `It's a passage ${exit.direction}`;
          msg +=
            exit.destination && exit.is.used
              ? ` to the ${exit.destination}`
              : ``;
          msg += `. `;
        }

        if (msg) this.game.print(msg, input.output_class);
        return true;
      } // if(input.parsedNoun1.matches.direction)

      // it's not global, just use its description
      if (!direct_object.is.global) {
        msg += direct_object.getDescription("look");
      }

      if (
        direct_object.isDOV("write") &&
        direct_object.written_strings.length &&
        direct_object.append_written_strings_to_description
      ) {
        msg += `Written on ${direct_object.articlename}`;

        if (1 === direct_object.written_strings.length) {
          msg += ` is a phrase: ${direct_object.written_strings[0]}`;
        } else {
          msg += ` are several phrases: `;
          for (var i = 0; i < direct_object.written_strings.length; i++) {
            msg += direct_object.written_strings[i];
            if (direct_object.written_strings.length > i + 1) msg += `, `;
          }
        }
        msg += `. `;
      }

      if (
        direct_object.is.typing_target &&
        direct_object.written_strings.length &&
        direct_object.append_written_strings_to_description
      ) {
        if (1 === direct_object.written_strings.length) {
          msg += `A line of input has been entered on the screen: `;
          msg += `<br> ${direct_object.written_strings[0]}`;
        } else {
          msg += `Several lines of input have been entered on the screen: `;
          for (var i = 0; i < direct_object.written_strings.length; i++) {
            msg += `<br> ${direct_object.written_strings[i]}`;
          }
        }
      }

      if (direct_object.hasAspectAt) {
        for (var i = 0; i < this.game.dictionary.prepositions.length; i++) {
          var preposition = this.game.dictionary.prepositions[i];
          if (direct_object.hasAspectAt(preposition)) {
            // if( preposition === "attached" )
            // {
            //   // important for attached - getCountOfListableContentsAt
            //   // may be inaccurate until attachments are known
            //   // direct_object.setAttachmentsKnown();
            //   direct_object.setAspectContentsKnown("attached",true);
            // }
            direct_object.setAspectContentsKnown(preposition, true);

            // 2023.04.06 not sure why this was here, it was preventing
            // printing descriptions of substances in containers
            // if( 0 >= direct_object.getCountOfListableContentsAt(preposition) )
            // {
            //   continue;
            // }

            if (!direct_object.aspects[preposition].list_in_examine) {
              continue;
            }
            if (
              preposition === "in" &&
              direct_object.isDOV("open") &&
              direct_object.dimensions.opacity === 1
            ) {
              // it's closed and opaque
              continue;
            }
            msg += direct_object.getPrintableListOfContentsAt(preposition);
          }
        }
      }

      // connections
      //if( direct_object.dov.tie?.with_params.connections.length )
      if (direct_object.DOVisConnectedToAnything("tie")) {
        let objects = { objects: direct_object.DOVgetConnections("tie") };
        msg += `${direct_object.Articlename} is tied to `;
        msg += this.game.getPrintableObjectList(objects);
        msg += direct_object.isIn(player)
          ? `, while $(we) hold an end of it`
          : ``;
        msg += `. `;
      }

      if (direct_object.IOVisConnectedToAnything("tie")) {
        let connections = direct_object.DOV.getConnections();
        for (let r = 0; r < connections.length; r++) {
          let rope = this.game.getAsset(connections[r]);
          msg += `Tied to ${direct_object.articlename} is a ${rope.name}`;
          if (1 < rope.DOVgetConnectionCount()) {
            let objects = {
              objects: rope.DOVgetConnections(),
              exclusions: direct_object.id,
            };
            msg += `, which is also tied to ${this.game.getPrintableObjectList(
              objects
            )}`;
          }
          msg += rope.isIn(player) ? `, while $(we) hold an end of it` : ``;
          msg += `. `;
        }
      }

      // if player input carefully examine
      if (-1 !== input.input_verb.indexOf("carefully")) {
        msg += direct_object.hasDescription("careful")
          ? direct_object.getDescription("careful")
          : `Careful examination reveals nothing new. `;
      }

      msg = msg || `${direct_object.Articlename} hasn't got a description. `;

      this.handleSuccess(msg, direct_object);
      return true;
    },
  };
})(); // examine