// Verb.js
(function () {
/*global adventurejs A*/
"use strict";
/**
* @class adventurejs.Verb
* @param {adventurejs.Game} game A reference to the game instance.
* @ajsnavheading FrameworkReference
* @summary Framework class that all verbs are instanced from.
* @classdesc
* <p>
* <strong>Verb</strong> is the base class for all Verb
* instances. {@link adventurejs} comes with over 100
* predefined Verbs, each with logic to support
* all of the predefined {@link adventurejs.Asset|Asset}
* classes. You can get pretty far using the predefined
* Verbs and Assets. Naturally, you're going to have
* ideas that don't fit the predefined logic. adventurejs
* provides several methods to modify Verbs, which range
* in complexity from making small edits all the way up
* to writing new Verbs from scratch.
* </p>
* <ul>
*
* <li>{@link adventurejs.Dictionary#patchVerb|patchVerb()}
* lets an author replace only selected properties
* and methods of any predefined Verb.</li>
*
* <li>{@link adventurejs.Dictionary#replaceVerb|replaceVerb()}
* lets an author completely replace any of the predefined
* Verbs.</li>
*
* <li>{@link adventurejs.Dictionary#combineVerbs|combineVerbs()}
* lets an author consolidate predefined Verbs. Some of
* the predefined Verbs exist to catch subtle distinctions
* that may not be necessary for your game. For instance,
* <code class="property">twist</code> and
* <code class="property">turn</code> are predefined
* as distinct Verbs. If you don't need that level of
* distinction, you can use combineVerbs to
* consolidate them.</li>
*
* <li>{@link adventurejs.Dictionary#disableVerbs|disableVerbs()}
* lets an author delete specified Verbs from their game's
* {@link adventurejs.Dictionary|Dictionary}. Useful when
* you don't want to support certain Verbs.</li>
*
* <li>{@link adventurejs.Dictionary#enableVerbs|enableVerbs()}
* lets an author re-enable Verbs that have been disabled.</li>
*
* <li>{@link adventurejs.Dictionary#disableAllVerbsBut|disableAllVerbsBut()}
* lets an author disable all but specified Verbs. Useful
* for creating a game with limited language. </li>
*
* <li>{@link adventurejs.Dictionary#createVerb|createVerb()}
* lets an author create a new Verb from scratch.</li>
*
* <li>{@link adventurejs.Dictionary#doVerb|doVerb()}
* lets an author call a Verb from custom code
* during runtime.</li>
*
* </ul>
* <h3 class="examples">Examples:</h3>
* <pre class="display"><code class="language-javascript"><h4>// patchVerb()</h4>
* MyGame.patchVerb({
* name: "crawl",
* prettyname: "wriggle", // change prettyname
* });
* </code></pre>
* <br>
* <pre class="display"><code class="language-javascript"><h4>//replaceVerb()</h4>
* MyGame.replaceVerb( "xyzzy", {
* name: "xyzzy",
* prettyname: "xyzzy",
* synonyms: [],
* do: function( params )
* {
* console.log( "verbs.do" );
* var msg = "Clever xyzzy response!";
* if(msg) this.game.print( msg, MyGame.input.output_class );
* return params
* },
* });
* </code></pre>
* <br>
* <pre class="display"><code class="language-javascript"><h4>// combineVerbs()</h4>
* MyGame.combineVerbs( [ "twist" ], "turn" );
* </code></pre>
* <br>
* <pre class="display"><code class="language-javascript"><h4>// disableVerbs()</h4>
* MyGame.disableVerbs( [ "lick", "eat" ] );
* </code></pre>
* <br>
* <pre class="display"><code class="language-javascript"><h4>// enableVerbs()</h4>
* MyGame.enableVerbs( [ "lick", "eat" ] );
* </code></pre>
* <br>
* <pre class="display"><code class="language-javascript"><h4>// disableAllVerbsBut()</h4>
* MyGame.disableAllVerbsBut( [ "look", "examine" ] );
* </code></pre>
* <br>
* <pre class="display"><code class="language-javascript"><h4>// createVerb()</h4>
* MyGame.createVerb({
* name: "blab_about_noun1",
* prettyname: "blab about",
* verb_prep_noun: [ "blab about" ], // blab about thing
* verb_noun_prep_noun: [ "blab about" ], // blab person about thing
* doTry: function( input )
* {
* [insert logic here]
* return true;
* },
* doSuccess: function( input )
* {
* [insert logic here]
* return true;
* },
* });
* </code></pre>
* <br>
* <pre class="display"><code class="language-javascript"><h4>// doVerb() (call during runtime)</h4>
* this.game.dictionary.doVerb("xyzzy");
* </code></pre>
* <p>
* For more information about creating Verbs or modifying Verbs, see
* <a href="/doc/Scripting_VerbSubscriptions.html">Verb Subscriptions</a>,
* <a href="/doc/Scripting_VerbPhases.html">Verb Phases</a>,
* <a href="/doc/Scripting_VerbActions.html">Verb Actions</a>,
* <a href="/doc/Verbs_VerbAnatomy.html">Verb Anatomy</a>,
* <a href="/doc/Verbs_VerbProcess.html">Verb Process</a>, or
* <a href="/doc/Verbs_ModifyVerbs.html">Modify Verbs</a>.
* </p>
*/
class Verb {
constructor(game) {
/**
* A reference back to the main {@link adventurejs.Game|Game} object.
* @var {Object} adventurejs.Verb#game
* @default {}
*/
this.game = game;
/**
* A shortcut to the main {@link adventurejs.Game|Game}
* {@link adventurejs.Dictionary|Dictionary}.
* @var {Object} adventurejs.Verb#dictionary
* @default {}
*/
this.dictionary = game.dictionary;
/**
* May be used to help narrow verb selections in ambiguous situations.
* @var {String} adventurejs.Verb#type
* @default ""
*
*/
this.type = {
travel: false,
manipulation: false,
direction: false,
locomotion: false,
};
/**
* Extension verbs may perform some contextual logic before forwarding
* to another verb for the bulk of logic, such as "crawl" -> "go".
* @var {String} adventurejs.Verb#extends
* @default ""
*
*/
this.extends = "";
/**
* String provided in Verb definition file (aka preverb).
* @var {String} adventurejs.Verb#name
* @default ""
*
*/
this.name = "";
/**
* String provided in verb definition file. The prettyname
* is used for printing, and can include spaces,
* ie ask prints as "ask about".
* @var {String} adventurejs.Verb#prettyname
*
*/
this.prettyname = "";
/**
* The past tense of the verb. May be used in output strings.
* @var {String} adventurejs.Verb#past_tense
*
*/
this.past_tense = "";
/**
* <code>state</code> is an optional property for verbs that apply
* state to assets, such as close and lock. For example, "close door"
* will set door.is.closed to true. When used, state will contain the
* state to be set true on an asset. In the case of close, its state
* would be "closed".
* @var {String} adventurejs.Verb#state
*/
this.state = "";
/**
* <code>unstate</code> is an optional property for verbs that unset
* state from assets, such as open and unlock. For example, "open door"
* will set door.is.closed to false. When used, unstate will contain the
* state to be set false on an asset. In the case of open, its unstate
* would be "closed".
* @var {String} adventurejs.Verb#unstate
*/
this.unstate = "";
/**
* <code>state_strings</code> is an optional property for verbs that is
* used to provide string substitutions for authors using the string
* substitution form of $(sink drain is| plugged or| unplugged).
* Because "unplugged" isn't a proper verb state, we'll use this as a
* reverse lookup to test whether the asset, sink_drain in this case,
* is subscribed to the relevant verb and has the specified state.
* state_strings only apply to direct objects.
* @var {String} adventurejs.Verb#state_strings
*/
this.state_strings = { state: "", unstate: "" };
/**
* <code>with_params</code> can contain properties specific to this verb
* that may be applied to assets which are objects of the verb. For example:
* the verb <code class="property">plugIn</code> has
* <code class="property">with_params.max_connections</code>,
* and in the case of something like a power cable that can be plugged in at
* both ends, an author could set its
* <code>asset.dov.plugIn.with_params.max_connections</code>
* to 2.
*/
this.with_params = {};
/**
* <code>linked_params</code> are params in with_params that are used
* to store the IDs of assets that share state as a result of being
* acted upon by this verb. For example: the verb
* <code class="property">plugIn</code> connects a direct object with
* an indirect object, and both assets need a reference back to the other.
*/
this.linked_params = [];
/**
* Verb.adjectives are for direction verbs so that, for example,
* 'south' can be associated with 'southern' and 'southernly'.
* @var {String} adventurejs.Verb#adjectives
*
*/
this.adjectives = [];
/**
* <strong>player_must_be</strong> sets conditions that the
* Player Character must meet in order for the Verb to act.
* @var {Object} adventurejs.Verb#player_must_be
* @default {}
*/
this.player_must_be = {
not_constrained: true,
not_on_floor: false,
not_under: false,
not_behind: false,
not_nested_elsewhere: false,
//not_on_target: false, // used for from/to
//on_origin: false, // used for from/to
};
/**
* Setting this to true allows you to write your own
* disambiguation script. Warning: going off road!
* Recommended for experienced Javascript users.
* @var {Boolean} adventurejs.Verb#let_verb_handle_disambiguation
* @default false
*/
this.let_verb_handle_disambiguation = false;
/**
* When input is parsed, parse the verb and then pass the
* remainder of the input to the verb as a string, for the
* verb to act on. Chief example is: "oops xxx" where we don't
* want to parse xxx, we just want to let oops use it as a
* substitute for last turn's unknown input.
* @var {Boolean} adventurejs.Verb#let_verb_handle_remaining_input
* @default false
*/
this.let_verb_handle_remaining_input = false;
/**
* Some types of objects can accept 'in' for 'on'
* interchangeably, such as 'sit in chair' / 'sit on chair',
* or 'lie in bed' / 'lie on bed'.
* @var {Boolean} adventurejs.Verb#in_can_mean_on
* @default false
*/
this.in_can_mean_on = false;
/**
* Set whether verb is a direction verb.
* @var {Boolean} adventurejs.Verb#is_direction
* @default false
*/
this.is_direction = false;
/**
* Set whether direction verb is a compass direction,
* meaning, it can be found on a compass rose.
* @var {Boolean} adventurejs.Verb#is_compass_direction
* @default false
*/
this.is_compass_direction = false;
/**
* Set whether direction verb is a relative direction
* such as those used on ships: port, starboard, etc.
* Also applies to left, right, forward, back, etc.
* @var {Boolean} adventurejs.Verb#is_spatial_direction
* @default false
*/
this.is_spatial_direction = false;
/**
* Set whether a direction can be referred to with an
* article, as in "there is a door to the north" vs
* "there is a door to starboard". This is a bit of mixed
* purpose because this property doesn't apply to the verb,
* but is stored in directionLookup for reference with
* directions.
* @var {Boolean} adventurejs.Verb#article
* @default false
*/
this.article = "";
/**
* When player travels, this string may be prepended before
* the verb name, such as "you walk to the north"
* @var {Boolean} adventurejs.Verb#direction_preposition
* @default ""
*/
this.direction_preposition = "";
this.phrase1 = new adventurejs.Phrase();
this.phrase2 = new adventurejs.Phrase();
this.phrase3 = new adventurejs.Phrase();
this.accepts_structures = [];
this.accepts_adverb = false;
this.accepts_adverbs = [];
/**
* To simplify identifying verbs in input,
* specifically with regards to adverbs & prepositions,
* we can provide a list of synonyms for the verb.
* The parser will look for these synonyms in the input
* and replace them with the verb name. Then, the verb
* can handle the adverb/preposition as it sees fit.
* @var {Object} adventurejs.Verb#input_substitutions
* @default {}
*/
this.input_substitutions = {};
/**
* Provides a simple method for an author to override all failure messages
* for a verb with one generic string.
* @var {String} adventurejs.Verb#override_verb_failure_msg
* @default undefined
*
*/
this.override_verb_failure_msg = "";
/**
* Provides a simple method for an author to override success messages
* for a verb with one generic string.
* @var {String} adventurejs.Verb#override_verb_success_msg
* @default undefined
*
*/
this.override_verb_success_msg = "";
this.msgNoObject = "";
this.msgNoAspect = "";
/**
* <code>related</code> is an array of related verbs, intended for use
* with tryToInferIndirectObject. Depending on a game's settings, some
* verbs may be applied automatically, but only when a user has already
* applied the verb themselves. For example, an author might not want a
* door to be opened automatically because the player should bring
* intention to the action of opening it, in order to appreciate the
* consequences. tryToInferIndirectObject considers whether a verb has
* previously been applied to an asset. In some cases, the verb's
* opposing verb may also be considered. For example, whether the player
* unlocked a door, or picked it, or locked it themselves, we consider
* them to have interacted with it.
* @var {Array} adventurejs.Verb#related
*/
this.related = [];
/**
* <code>enqueue_collections</code> if true allows a verb to
* unbundle the members of a collection in order to queue up
* separate actions for each. For example, "gems" is a collection
* that refers to three unique assets; "diamond", "emerald"
* and "ruby". If take.enqueue_collections is true, "take gems"
* will act individually on the diamond, the emerald and the ruby.
* Only applies to direct object.
* @var {Array} adventurejs.Verb#enqueue_collections
* @default false
*/
this.enqueue_collections = false;
return this;
}
/**
* Return uppercase name of the verb.
* @var {Getter} adventurejs.Verb#Name
* @default []
*/
get Name() {
return A.propercase(this.name);
}
/**
* Returns "try[Verb]This" for consistency with callAction()
* @var {Getter} adventurejs.Verb#tryVerbThis
*/
get tryVerbThis() {
return "try" + this.Name + "This";
}
/**
* Returns "try[Verb]WithThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#tryVerbWithThis
*/
get tryVerbWithThis() {
return "try" + this.Name + "WithThis";
}
/**
* Returns "try[Verb]FromThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#tryVerbFromThis
*/
get tryVerbFromThis() {
return "try" + this.Name + "FromThis";
}
/**
* Returns "try[Verb]ThisWithThat" for consistency with callAction()
* @var {Getter} adventurejs.Verb#tryVerbThisWithThat
*/
get tryVerbThisWithThat() {
return "try" + this.Name + "ThisWithThat";
}
/**
* Returns "try[Verb]ThatWithThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#tryVerbThatWithThis
*/
get tryVerbThatWithThis() {
return "try" + this.Name + "ThatWithThis";
}
/**
* Returns "try[Verb]ThisFromThat" for consistency with callAction()
* @var {Getter} adventurejs.Verb#tryVerbThisFromThat
*/
get tryVerbThisFromThat() {
return "try" + this.Name + "ThisFromThat";
}
/**
* Returns "try[Verb]ThatFromThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#tryVerbThatFromThis
*/
get tryVerbThatFromThis() {
return "try" + this.Name + "ThatFromThis";
}
/**
* Returns "do[Verb]This" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerb
*/
get doVerb() {
return `do${this.Name}`;
}
/**
* Returns "do[Verb]This" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerbThis
*/
get doVerbThis() {
return `do${this.Name}This`;
}
/**
* Returns "do[Verb]FromThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerbFromThis
*/
get doVerbFromThis() {
return `do${this.Name}FromThis`;
}
/**
* Returns "do[Verb]WithThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerbWithThis
*/
get doVerbWithThis() {
return `do${this.Name}WithThis`;
}
/**
* Returns "do[Verb]ThisWithThat" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerbThisWithThat
*/
get doVerbThisWithThat() {
return `do${this.Name}ThisWithThat`;
}
/**
* Returns "do[Verb]ThatWithThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerbThatWithThis
*/
get doVerbThatWithThis() {
return `do${this.Name}ThatWithThis`;
}
/**
* Returns "do[Verb]ThisFromThat" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerbThisFromThat
*/
get doVerbThisFromThat() {
return `do${this.Name}ThisFromThat`;
}
/**
* Returns "do[Verb]ThatFromThis" for consistency with callAction()
* @var {Getter} adventurejs.Verb#doVerbThatFromThis
*/
get doVerbThatFromThis() {
return `do${this.Name}ThatFromThis`;
}
/**
* <strong>synonyms</strong> provide alternate words for verbs,
* such as "get" for "take".
* @var {Getter/Setter} adventurejs.Verb#synonyms
* @default []
*/
get synonyms() {
return this._synonyms;
}
set synonyms(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._synonyms)) {
this._synonyms = [];
}
// apply new words
this._synonyms = arr;
// ensure verb name is in synonyms
if (-1 === this._synonyms.indexOf(this.name)) {
this._synonyms.push(this.name);
}
}
/**
* For phrases like "jump from branch to vine" or
* "look at sun with glasses", where we have a verb + preposition
* followed by a noun and then another preposition
* @var {Array} adventurejs.Verb#verb_prep_noun_prep_noun
* @default []
*/
get verb_prep_noun_prep_noun() {
return this._verb_prep_noun_prep_noun;
}
set verb_prep_noun_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (!Array.isArray(this._verb_prep_noun_prep_noun)) {
this._verb_prep_noun_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_prep_noun_prep_noun.length) {
for (var i = this._verb_prep_noun_prep_noun.length; i > -1; i--) {
var pair = [this._verb_prep_noun_prep_noun[i], this.name];
var index = A.indexOfSubarray(
pair,
this.dictionary.verb_prep_noun_prep_nouns
);
if (-1 !== index) {
this.dictionary.verb_prep_noun_prep_nouns.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_prep_noun_prep_nouns.push([arr[i], this.name]);
}
}
// apply new words
this._verb_prep_noun_prep_noun = arr;
}
/**
* For verb/noun pairs with a trailing preposition,
* or more likely a direction, such as "push bed north".
*
* When player input is parsed, they'll be concatenated,
* eg to "pushnorth bed".
* @var {Array} adventurejs.Verb#verb_noun_prep
* @default []
*/
get verb_noun_prep() {
return this._verb_noun_prep;
}
set verb_noun_prep(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_noun_prep)) {
this._verb_noun_prep = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_noun_prep.length) {
for (var i = this._verb_noun_prep.length; i > -1; i--) {
var pair = [this._verb_noun_prep[i], this.name];
var index = A.indexOfSubarray(pair, this.dictionary.verb_noun_preps);
if (-1 !== index) {
this.dictionary.verb_noun_preps.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_noun_preps.push([arr[i], this.name]);
}
}
// apply new words
this._verb_noun_prep = arr;
}
/**
* For verb/preposition pairs separated by a space,
* such as "go to" or "look at".
*
* When player input is parsed, they'll be concatenated,
* eg "go to" to "goTo".
* @var {Array} adventurejs.Verb#verb_prep_noun
* @default []
*/
get verb_prep_noun() {
return this._verb_prep_noun;
}
set verb_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_prep_noun)) {
this._verb_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_prep_noun.length) {
for (var i = this._verb_prep_noun.length; i > -1; i--) {
var pair = [this._verb_prep_noun[i], this.name];
var index = A.indexOfSubarray(pair, this.dictionary.verb_prep_nouns);
if (-1 !== index) {
this.dictionary.verb_prep_nouns.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_prep_nouns.push([arr[i], this.name]);
}
}
// apply new words
this._verb_prep_noun = arr;
}
/**
* For compound preps separated by spaces, verb/prep/prep,
* such as "get out of"
* @var {Array} adventurejs.Verb#verb_prep_prep_noun
* @default []
*/
get verb_prep_prep_noun() {
return this._verb_prep_prep_noun;
}
set verb_prep_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_prep_prep_noun)) {
this._verb_prep_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_prep_prep_noun.length) {
for (var i = this._verb_prep_prep_noun.length; i > -1; i--) {
var pair = [this._verb_prep_prep_noun[i], this.name];
var index = A.indexOfSubarray(
pair,
this.dictionary.verb_prep_prep_nouns
);
if (-1 !== index) {
this.dictionary.verb_prep_prep_nouns.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_prep_prep_nouns.push([arr[i], this.name]);
}
}
// apply new words
this._verb_prep_prep_noun = arr;
}
/**
* For three part compound preps, verb/prep/prep/prep,
* such as "get out from behind"
* @var {Array} adventurejs.Verb#verb_prep_prep_prep_noun
* @default []
*/
get verb_prep_prep_prep_noun() {
return this._verb_prep_prep_prep_noun;
}
set verb_prep_prep_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_prep_prep_prep_noun)) {
this._verb_prep_prep_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_prep_prep_prep_noun.length) {
for (var i = this._verb_prep_prep_prep_noun.length; i > -1; i--) {
var pair = [this._verb_prep_prep_prep_noun[i], this.name];
var index = A.indexOfSubarray(
pair,
this.dictionary.verb_prep_prep_prep_nouns
);
if (-1 !== index) {
this.dictionary.verb_prep_prep_prep_nouns.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_prep_prep_prep_nouns.push([arr[i], this.name]);
}
}
// apply new words
this._verb_prep_prep_prep_noun = arr;
}
/**
* For verb/preposition pairs separated by another word,
* usually a noun,
* such as "lock door with key" or "take sword from stone".
* When player input is parsed, they'll be concatenated,
* eg to "lockwith door key" or "takefrom sword stone".
* <br><br>
* Though verb_prep_noun and verb_noun_prep_noun look similar, the reason
* they are separate fields is because we have to use
* different regex patterns to find each type in user input.
* @var {Array} adventurejs.Verb#verb_noun_prep_noun
* @default []
*/
get verb_noun_prep_noun() {
return this._verb_noun_prep;
}
set verb_noun_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_noun_prep_noun)) {
this._verb_noun_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_noun_prep_noun.length) {
for (var i = this._verb_noun_prep_noun.length; i > -1; i--) {
var pair = [this._verb_noun_prep_noun[i], this.name];
var index = A.indexOfSubarray(
pair,
this.dictionary.verb_noun_prep_nouns
);
if (-1 !== index) {
this.dictionary.verb_noun_prep_nouns.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_noun_prep_nouns.push([arr[i], this.name]);
}
}
// apply new words
this._verb_noun_prep_noun = arr;
}
/**
* For a verb phrase with two nouns and two prepositions.
* For example, in the phrase "take skateboard from under bed",
* we're looking for "take" and "from" and "under",
* and we would parse the phrase as "takefromunder skateboard bed"
* @var {Array} adventurejs.Verb#verb_noun_prep_prep_noun
* @default []
*/
get verb_noun_prep_prep_noun() {
return this._verb_noun_prep;
}
set verb_noun_prep_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_noun_prep_prep_noun)) {
this._verb_noun_prep_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_noun_prep_prep_noun.length) {
for (var i = this._verb_noun_prep_prep_noun.length; i > -1; i--) {
var pair = [this._verb_noun_prep_prep_noun[i], this.name];
var index = A.indexOfSubarray(
pair,
this.dictionary.verb_noun_prep_prep_nouns
);
if (-1 !== index) {
this.dictionary.verb_noun_prep_prep_nouns.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_noun_prep_prep_nouns.push([arr[i], this.name]);
}
}
// apply new words
this._verb_noun_prep_prep_noun = arr;
}
/**
* For a verb phrase with three nouns and two prepositions.
* For example, in the phrase "tie boat to pier with rope",
* we're looking for "tie" and "to" and "with",
* and we would parse the phrase as "tietowith boat pier rope"
* @var {Array} adventurejs.Verb#verb_noun_prep_noun_prep_noun
* @default []
*/
get verb_noun_prep_noun_prep_noun() {
return this._verb_noun_prep_noun_prep_noun;
}
set verb_noun_prep_noun_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_noun_prep_noun_prep_noun)) {
this._verb_noun_prep_noun_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_noun_prep_noun_prep_noun.length) {
for (var i = this._verb_noun_prep_noun_prep_noun.length; i > -1; i--) {
var pair = [this._verb_noun_prep_noun_prep_noun[i], this.name];
var index = A.indexOfSubarray(
pair,
this.dictionary.verb_noun_prep_noun_prep_nouns
);
if (-1 !== index) {
this.dictionary.verb_noun_prep_noun_prep_nouns.splice(index, 1);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_noun_prep_noun_prep_nouns.push([
arr[i],
this.name,
]);
}
}
// apply new words
this._verb_noun_prep_noun_prep_noun = arr;
}
/**
* For a verb phrase with three nouns and three prepositions.
* For example, in the phrase
* "swing from branch to tree on vine", we're looking for "swing from with on".
* @var {Array} adventurejs.Verb#verb_prep_noun_prep_noun_prep_noun
* @default []
*/
get verb_prep_noun_prep_noun_prep_noun() {
return this._verb_prep_noun_prep_noun_prep_noun;
}
set verb_prep_noun_prep_noun_prep_noun(arr) {
// don't know how to initialize Object.defineProperty as an array
if (false === Array.isArray(this._verb_prep_noun_prep_noun_prep_noun)) {
this._verb_prep_noun_prep_noun_prep_noun = [];
}
// remove all prior words from dictionary,
// in case this verb is being redefined
if (0 < this._verb_prep_noun_prep_noun_prep_noun.length) {
for (
var i = this._verb_prep_noun_prep_noun_prep_noun.length;
i > -1;
i--
) {
var pair = [this._verb_prep_noun_prep_noun_prep_noun[i], this.name];
var index = A.indexOfSubarray(
pair,
this.dictionary.verb_prep_noun_prep_noun_prep_nouns
);
if (-1 !== index) {
this.dictionary.verb_prep_noun_prep_noun_prep_nouns.splice(
index,
1
);
}
}
}
// add new words to lookup
if (arr.length > 0) {
for (var i = 0; i < arr.length; i++) {
this.dictionary.verb_prep_noun_prep_noun_prep_nouns.push([
arr[i],
this.name,
]);
}
}
// apply new words
this._verb_prep_noun_prep_noun_prep_noun = arr;
}
/**
* <strong>Verb.do</strong> is a coordinating method that
* sequences six other submethods in a series. In the case of
* Verb instances that can act on a collection of
* {@link adventurejs.Asset|Assets} in a single turn, Verb.do
* only fires once, but it loops through the Asset collection
* and calls each submethod for every Asset in the collection.
* The sequence is:
* <br><br>
* do ->
* <ul>
* <li><a href="#doBeforeTry">doBeforeTry</a> (hook for authors)</li>
* <li><a href="#doTry">doTry</a></li>
* <li><a href="#doAfterTry">doAfterTry</a> (hook for authors)</li>
* <li><a href="#doBeforeSuccess">doBeforeSuccess</a> (hook for authors)</li>
* <li><a href="#doSuccess">doSuccess</a></li>
* <li><a href="#doAfterSuccess">doAfterSuccess</a> (hook for authors)</li>
* </ul>
* The two key submethods are Verb.doTry and Verb.doSuccess.
* For most Verb instances, these two methods contain the bulk
* of the logic particular to this Verb. Verb.doTry determines
* whether a Verb can act on an Asset, and if it can't,
* prints an error message
* to {@link adventurejs.Display|Display}.
* Verb.doSuccess applies the Verb to the Asset: updates the game
* state, assembles dynamic output, and prints the results to Display.
* <br><br>
* A Verb instance isn't required to use all of these methods.
* Some Verbs may bypass Verb.doTry
* because no special conditions are required to apply the Verb.
* Some specialized Verbs such as {@link oops} and {@link undo}
* override Verb.do entirely and don't use any submethods.
* <br><br>
* The other four submethods – Verb.doBeforeTry, Verb.doAfterTry,
* Verb.doBeforeSuccess, and Verb.doAfterSuccess – exist to
* provide optional hooks for authors to add custom interactions
* with individual Assets.
* For more information about Verb Actions and Verb Phases, see
* <a href="/doc/Scripting_VerbActions.html">Verb Actions</a>
* and
* <a href="/doc/Scripting_VerbPhases.html">Verb Phases</a>.
* <br><br>
* And so, the first thing Verb.do does is to verify that each
* method exists on the Verb instance. If the submethod exists,
* it is called. Each submethod sends a return to Verb.do.
* <br><br>
* If the Verb is acting on a collection,
* a false return means that the Asset currently being acted
* on has responded in a way that blocks further parsing, and
* brings this turn to a halt.
* A null return means that the Asset currently being acted
* on has concluded its own parsing, but not in such a way as
* to block further parsing, and Verb.do moves on to the next Asset.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#do
*/
do() {
this.game.log("log", "high", "Verb.js > do " + this.name, "Verb");
var input = this.game.getInput();
var input_copy;
var msg = "";
if (
-1 < input.verb_chain.indexOf(this.name) &&
!input.allow_circular_verb
) {
msg = "Error: Circular verb call! See console for more information. ";
if (msg) this.game.print(msg, "error");
msg = msg + "Circular verb call:";
for (var i = 0; i < input.verb_chain.length; i++) {
msg += "\n - " + input.verb_chain[i];
}
this.game.log("warn", "critical", msg, "Verb");
return false;
} else {
input.allow_circular_verb = false;
input.verb_chain.push(this.name);
}
var qualifiedCount = 1;
if (input.parsedNoun1) {
qualifiedCount = input.parsedNoun1.matches.qualified.length;
}
// "take all" for example will iterate through all available assets
// because verbs can mutate input, if we're iterating,
// make a copy of the original input
// to use as a base for each iteration
if (qualifiedCount > 1) {
input_copy = new adventurejs.Input({ game_name: this.game.game_name });
input_copy = Object.assign(input_copy, input);
}
for (var i = 0; i < qualifiedCount; i++) {
if (i > 0) {
// push a new input to the input history...
this.game.parser.input_history.unshift(
new adventurejs.Input({ game_name: this.game.game_name })
);
// and copy the original input onto it
this.game.parser.input_history[0] = Object.assign(
this.game.parser.input_history[0],
input_copy
);
}
let results;
if (input.parsedNoun1) {
input.parsedNoun1.matches.qualifiedIndex = i;
}
msg = this.name + ".js > doBeforeTry. ";
this.game.log("log", "high", msg, "Verb");
results = this.tryPhaseHook("doBeforeTry");
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
// advance to next step
msg = this.name + ".js > handleActions('try'). ";
this.game.log("log", "high", msg, "Verb");
results = this.handleActions("try");
//if ("undefined" !== typeof results) return results;
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
msg = this.name + ".js > Try. ";
this.game.log("log", "high", msg, "Verb");
results = this.doTry();
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
// advance to next step
msg = this.name + ".js > doAfterTry. ";
this.game.log("log", "high", msg, "Verb");
results = this.tryPhaseHook("doAfterTry");
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
// advance to next step
msg = this.name + ".js > doBeforeSuccess. ";
this.game.log("log", "high", msg, "Verb");
results = this.tryPhaseHook("doBeforeSuccess");
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
// advance to next step
msg = this.name + ".js > handleActions('do'). ";
this.game.log("log", "high", msg, "Verb");
results = this.handleActions("do");
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
msg = this.name + ".js > Success. ";
this.game.log("log", "high", msg, "Verb");
results = this.doSuccess();
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
// advance to next step
msg = this.name + ".js > doAfterSuccess. ";
this.game.log("log", "high", msg, "Verb");
// results = this.doAfterSuccess();
results = this.tryPhaseHook("doAfterSuccess");
if (false === results) return false; // end turn
if (null === results) continue; // advance to next object
// complete handling for this object
} // forloop qualifiedCount
} // do
/**
* <strong>doTry</strong> typically contains all the specific
* logic needed to determine if this Verb can act on the specified
* {@link adventurejs.Asset|Asset}. (We already applied
* some general logic supplied by
* {@link adventurejs.NounMustBe|NounMustBe} before arriving here.)
* For information about modifying verbs, see
* <a href="/doc/Verbs_ModifyVerbs.html">Modify Verbs</a>.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#doTry
*/
doTry() {
return true;
}
/**
* <strong>handleActions</strong> attempts to call any
* <a href="Scripting_VerbActions.html">verb actions</a>
* that match the current assets and sentence structure.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#handleActions
*/
handleActions(dotry) {
let input = this.game.getInput();
let Input_verb = A.propercase(input.input_verb);
let player = this.game.getPlayer();
let direct_object = input.getAsset(1);
let direct_preposition = input.getPreposition(1);
let Direct_preposition = A.propercase(direct_preposition);
let indirect_object = input.getAsset(2);
let indirect_preposition = input.getPreposition(2);
let Indirect_preposition = A.propercase(indirect_preposition);
let indirect_object2 = input.getAsset(3);
let indirect_preposition2 = A.propercase(input.getPreposition(3));
let Indirect_preposition2 = A.propercase(indirect_preposition2);
let hook = "";
let results;
switch (input.getStructure()) {
case "verb":
// ex player.tryTest
results = player.callAction(`${dotry}${Input_verb}`);
break;
case "verb preposition":
// ex: player.tryTestIn
results = player.callAction(
`${dotry}${Input_verb}${Direct_preposition}`
);
break;
case "verb noun":
// ex: asset.tryTestThis
results = direct_object.callAction(`${dotry}${Input_verb}This`);
break;
case "verb preposition noun":
// ex: asset1.tryTestToThis asset2
results = direct_object.callAction(
`${dotry}${Input_verb}${Direct_preposition}This`
);
break;
case "verb noun noun":
// ex: asset1.tryTestThisThat asset2
results = direct_object.callAction(`${dotry}${Input_verb}ThisThat`);
if ("undefined" !== typeof results) return results;
// ex: asset2.tryTestThatThis asset2
results = indirect_object.callAction(`${dotry}${Input_verb}ThatThis`);
break;
case "verb noun preposition noun":
// ex: asset1.tryTestThisToThat asset2
results = direct_object.callAction(
`${dotry}${Input_verb}This${Indirect_preposition}That`
);
if ("undefined" !== typeof results) return results;
break;
case "verb preposition noun preposition noun":
// ex: asset1.tryTestThisToThat asset2
results = direct_object.callAction(
`${dotry}${Input_verb}${Direct_preposition}This${Indirect_preposition}That`
);
if ("undefined" !== typeof results) return results;
break;
case "verb noun preposition noun preposition noun":
// ex: asset1.tryTestThisFromThatToThat asset2
hook = `${dotry}${Input_verb}This${Indirect_preposition}That${Indirect_preposition2}Other`;
results = direct_object.callAction(hook);
if ("undefined" !== typeof results) return results;
break;
case "verb preposition noun preposition noun preposition noun":
// ex: asset1.tryTestFromThisToThatWithThat asset2
hook = `${dotry}${Input_verb}${Direct_preposition}This${Indirect_preposition}That${Indirect_preposition2}Other`;
results = direct_object.callAction(hook, indirect_object);
if ("undefined" !== typeof results) return results;
break;
}
return results;
}
/**
* <strong>doSuccess</strong> typically contains all the code
* needed to apply this Verb to the specified
* {@link adventurejs.Asset|Asset} once it has successfully
* passed through all of our conditional logic. doBeforeSuccess
* and doAfterSuccess are provided so that authors can
* apply custom success code on an item-by-item basis,
* but it is also possible to globally modify doSuccess.
* For information about modifying verbs, see
* <a href="/doc/Verbs_ModifyVerbs.html">Modify Verbs</a>.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#doSuccess
*/
doSuccess() {
return true;
}
tryPhaseHook(phase) {
//console.warn("tryPhaseHook", phase);
var input = this.game.getInput();
for (var i = 1; i <= 3; i++) {
var asset = input.getAsset(i);
if (!asset) continue;
if (phase === "doBeforeTry") asset.incrementTryVerbCount(this.name, i);
let fx;
// does this asset have a verb phase?
if (
(i === 2 || i === 3) &&
asset.iov[this.name] &&
"function" === typeof asset.iov[this.name][phase]
) {
fx = asset.iov[this.name][phase];
} else if (
(i === 2 || i === 1) &&
asset.dov[this.name] &&
"function" === typeof asset.dov[this.name][phase]
) {
fx = asset.dov[this.name][phase];
}
// no verb phase?
if (!fx) continue;
var results = fx.call(asset, { index: i, verb: this.name });
if ("undefined" !== typeof results) {
var msg = `Verb.js > ${asset.name} ${this.name} ${[
phase,
]} returned ${results}. `;
this.game.log("log", "critical", msg, "Verb");
return results;
}
} // for
return true;
}
/**
* <strong>tryToInferIndirectObject</strong> is called by some verbs
* when they receive a direct object with no indirect object, to
* test whether an indirect object can be inferred. In order to be
* inferred, indirect object must be in player inventory.
* If player hasn't already interacted with direct object and
* game.settings.infer_indirect_objects_only_after_interaction
* is true, tryToInferIndirectObject will fail regardless of other
* circumstances.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#tryToInferIndirectObject
* @param {Object} direct_object
* @param {Boolean} handle_input If true, updates the global input object
* per standard specs used by most (but not all) of the verb instances
* that call this method.
* @returns {Object}
*/
tryToInferIndirectObject(direct_object, handle_input) {
if (
!this.game.settings.try_to_infer_indirect_objects ||
!direct_object ||
!(direct_object instanceof adventurejs.Matter)
) {
return { fail: true };
}
var input = this.game.getInput();
var related_verbs = this.related;
related_verbs.push(this.name);
var player = this.game.getPlayer();
var found;
var prompt = false;
// player might have an asset, but has never interacted with this
// in which case game settings determine whether to infer asset
if (
this.game.settings.infer_indirect_objects_only_after_interaction &&
!direct_object.didDoVerbs(related_verbs)
) {
prompt = true;
}
// ok to try to infer asset
// is player carrying a relevant asset?
found = player.getIOVkeys(this.name, direct_object);
// player isn't carrying a tool for this
if (!found || !found.length) {
prompt = true;
}
if (
found.length > 1 &&
this.game.settings.if_inferred_multiple_indirect_objects_then_prompt
) {
prompt = true;
}
if (prompt && handle_input) {
input.setPreposition(2, "with");
input.setSoftPrompt({
noun2: true,
structure: "verb noun preposition noun",
});
return { prompt: true };
}
if (handle_input) {
input.setNewPhrase({
asset: found[0],
preposition: "with",
});
input.setStructure("verb noun preposition noun");
}
return { success: true, indirect_object: found[0] };
}
/**
* <strong>tryToPutThisInThatAspect</strong>
* checks to see if a asset can be placed within
* the specified aspect of another specified asset.
* For example, "put sword in stone" and
* "push stone into depression" would both be
* tested with this function.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#tryToPutThisInThatAspect
* @param {Object} direct_object
* @param {String} preposition
* @param {Object} indirect_object
* @returns {Object}
*/
tryToPutThisInThatAspect(direct_object, preposition, indirect_object) {
var response = { fail: false, msg: "", status: "", end_turn: false };
var asset_aspect;
var msg = "";
var can = true;
if (
!direct_object ||
!(direct_object instanceof adventurejs.Tangible) ||
!indirect_object ||
!(indirect_object instanceof adventurejs.Tangible) ||
!preposition ||
!indirect_object.hasAspectAt(preposition)
) {
this.game.debug(
`F1670 | Verb.js via ${this.name}.js | received bad request `
);
if (!indirect_object.hasAspectAt(preposition)) {
msg += `$(We) can't put anything ${preposition} ${indirect_object.articlename}. `;
} else {
msg += this.game.parser.getUnparsedMessage(
this.game.getInput().input
);
}
response = {
fail: true,
msg: msg,
status: "bad_request",
end_turn: false,
};
return response;
}
// is indirect_object closed?
if (
"in" === preposition &&
indirect_object.isDOV("close") &&
indirect_object.is.closed
) {
// @TODO automatically open the thing, if context allows it
this.game.debug(
`F1671 | Verb.js via ${this.name}.js | indirect_object.id.is.closed `
);
msg += indirect_object.Articlename + " is closed.";
this.handleFailure(msg);
return false;
}
// does indirect object aspect limit what assets can be put in it?
var with_classes = indirect_object.aspects[preposition].with_classes;
if (with_classes.length > 0) {
can = false;
for (var i = 0; i < with_classes.length; i++) {
var clas = with_classes[i];
if (direct_object instanceof adventurejs[clas]) {
can = true;
break;
}
}
if (!can) {
this.game.debug(
`F1661 | Verb.js via ${this.name}.js | ${direct_object.id}.class ${direct_object.class} is not among ${indirect_object.id}.aspects.${preposition}.with_classes `
);
msg += `${direct_object.Articlename} can't be placed ${preposition} ${indirect_object.articlename}. `;
response = {
fail: true,
msg: msg,
status: "with_classes",
end_turn: false,
};
return response;
}
}
// does indirect object aspect limit what classes can be put in it?
var with_assets = indirect_object.aspects[preposition].with_assets;
if (with_assets.length > 0) {
can = false;
for (var i = 0; i < with_assets.length; i++) {
if (direct_object.id === with_assets[i]) {
can = true;
break;
}
}
if (!can) {
this.game.debug(
`F1662 | Verb.js via ${this.name}.js | ${direct_object.id} is not among ${indirect_object.id}.aspects.${preposition}.with_assets `
);
msg += `${direct_object.Articlename} can't be placed ${preposition} ${indirect_object.articlename}. `;
response = {
fail: true,
msg: msg,
status: "with_assets",
end_turn: false,
};
return response;
}
}
asset_aspect = indirect_object.getAspectAt(preposition);
if (
asset_aspect.maxsize > -1 &&
direct_object.dimensions.size > asset_aspect.maxsize
) {
this.game.debug(
`F1667 | Verb.js via ${this.name}.js | ${direct_object.id}.dimensions.size > ${indirect_object.id}.aspects.${preposition}.maxsize `
);
msg += `${direct_object.Articlename} doesn't fit ${preposition} ${indirect_object.articlename}. `;
response = {
fail: true,
msg: msg,
status: "maxsize",
end_turn: false,
};
return response;
}
if (
asset_aspect.maxweight > -1 &&
direct_object.dimensions.weight > asset_aspect.maxweight
) {
this.game.debug(
`F1668 | Verb.js via ${this.name}.js | ${direct_object.id}.dimensions.weight > ${indirect_object.id}.aspects.${preposition}.maxweight `
);
msg += `${direct_object.Articlename} is too heavy to ${this.name} ${preposition} ${indirect_object.articlename}. `;
response = {
fail: true,
msg: msg,
status: "maxweight",
end_turn: false,
};
return response;
}
if (
asset_aspect.maxcount > -1 &&
asset_aspect.contents.length >= asset_aspect.maxcount
) {
this.game.debug(
`F1669 | Verb.js via ${this.name}.js | ${indirect_object.id}.aspects.${preposition}.contents.length >= ${indirect_object.id}.aspects.${preposition}.maxcount `
);
msg += `Nothing ${asset_aspect.maxcount > 0 ? "more " : ""} can be ${
this.past_tense
} ${preposition} ${indirect_object.articlename}. `;
response = {
fail: true,
msg: msg,
status: "maxcount",
end_turn: true,
};
return response;
}
return response;
}
/**
* Unused.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#validate
*/
validate() {}
/**
* If Verb is a direction, <strong>initialize</strong> adds
* it to game.dictionary.directionLookup.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#initialize
* @todo How does patchVerb handle initialization?
*/
initialize() {
if (!this.prettyname) {
this.prettyname = this.name;
}
// TODO how does patch handle this?
// if(-1 === this.synonyms.indexOf( this.name ) ) {
// this.synonyms.push( this.name );
// }
// TODO how does patch handle this?
if (this.is_direction) {
// update lookup
this.dictionary.directionLookup[this.name] = {
synonyms: this.synonyms,
adjectives: this.adjectives,
article: this.article,
};
}
if (this.state_string) {
this.game.dictionary.verb_state_lookup[this.state_string] = this.name;
}
if (this.unstate_string) {
this.game.dictionary.verb_state_lookup[this.unstate_string] = this.name;
}
}
/**
* <strong>enqueueCollection</strong> takes a collection of
* Assets and enqueues them to game.parser for sequential
* handling.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#enqueueCollection
*/
enqueueCollection(object) {
this.game.log("log", "high", "Verb.js.enqueueCollection(, 'Verb' ) ");
for (var i = 0; i < object.collection.length; i++) {
var linefeed = i === object.collection.length ? true : undefined;
this.game.parser.input_queue.push({
input: this.name + " " + object.collection[i],
output_class: "concatenate_output",
linefeed: linefeed,
});
}
return false;
// alternately
//var msg = "You'll have to look in " + object.articlename + " individually.";
//if(msg) this.game.print( msg, output_class );
//return null;
}
/**
* Provides a chainable shortcut method for setting a number of properties on the instance.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#set
* @param {Object} props A generic object containing properties to copy to the DisplayObject instance.
* @returns {adventurejs.Verb} Returns the instance the method is called on (useful for chaining calls.)
* @chainable
*/
set(props) {
return A.deepSet.call(this.game, props, this);
}
/**
* <strong>handleFailure</strong> prints either a given fail message
* or a generic fail msg if one is specified.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#handleFailure
*/
handleFailure(msg) {
var input = this.game.getInput();
var noun1 = input.getAsset(1);
var noun2 = input.getAsset(2);
var noun3 = input.getAsset(3);
var results;
msg = A.getSAF.call(this.game, this.override_verb_failure_msg) || msg;
if (noun1?.dov[this.name]) {
if (
1 >= noun1.DOVdidTryCount(this.name) &&
noun1.dov[this.name].on_first_failure
) {
results = A.getSAF.call(
this.game,
noun1.dov[this.name].on_first_failure,
noun1
);
} else {
results = A.getSAF.call(
this.game,
noun1.dov[this.name].on_failure,
noun1
);
}
if (results && "string" === typeof results) msg += results;
}
if (noun2?.iov[this.name]) {
if (
1 >= noun2.IOVdidTryCount(this.name) &&
noun2.iov[this.name].on_first_failure
) {
results = A.getSAF.call(
this.game,
noun2.iov[this.name].on_first_failure,
noun2
);
} else {
results = A.getSAF.call(
this.game,
noun2.iov[this.name].on_failure,
noun2
);
}
if (results && "string" === typeof results) msg += results;
} else if (noun2?.dov[this.name]) {
if (
1 >= noun2.DOVdidTryCount(this.name) &&
noun2.dov[this.name].on_first_failure
) {
results = A.getSAF.call(
this.game,
noun2.dov[this.name].on_first_failure,
noun2
);
} else {
results = A.getSAF.call(
this.game,
noun2.dov[this.name].on_failure,
noun2
);
}
if (results && "string" === typeof results) msg += results;
}
if (noun3?.iov[this.name]) {
if (
1 >= noun3.IOVdidTryCount(this.name, 3) &&
noun3.iov[this.name].on_first_failure
) {
results = A.getSAF.call(
this.game,
noun3.iov[this.name].on_first_failure,
noun3
);
} else {
results = A.getSAF.call(
this.game,
noun3.iov[this.name].on_failure,
noun3
);
}
if (results && "string" === typeof results) msg += results;
}
if (msg) {
this.game.print(msg, this.game.getInput().output_class);
}
}
/**
* <strong>handleSuccess</strong> prints the provided
* success message or a generic one that has been
* defined by author. It also checks direct and indirect
* objects for custom verb subscription on_success
* results and tryDestroy results.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#handleSuccess
*/
handleSuccess(msg, object) {
var input = this.game.getInput();
var noun1 = input.getAsset(1);
var noun2 = input.getAsset(2);
var noun3 = input.getAsset(3);
var results;
msg = A.getSAF.call(this.game, this.override_verb_success_msg) || msg;
if (noun1) {
noun1.incrementDoVerbCount(this.name, 1);
if (noun1.dov[this.name]) {
// if( !noun1.DOVdidDo(this.name)
if (
1 === noun1.DOVdidDoCount(this.name) &&
noun1.dov[this.name].on_first_success
) {
results = A.getSAF.call(
this.game,
noun1.dov[this.name].on_first_success,
noun1
);
} else {
results = A.getSAF.call(
this.game,
noun1.dov[this.name].on_success,
noun1
);
}
if (results && "string" === typeof results) msg += results;
// then disable?
if (noun1.dov[this.name].then_disable) {
noun1.dov[this.name].enabled = false;
}
// destroy after using?
results = noun1.tryDestroyDirectObjectAfterUsing(this.name);
if (results.destroy)
msg += results.msg
? results.msg
: `${noun1.Articlename} snaps into pieces. `;
} else {
// warn because we probably shouldn't have got here without this
this.game.log(
"warn",
0,
`${noun1.id} is not direct subscribed to ${this.name}`,
"Verb"
);
}
} // noun1
if (noun2) {
noun2.incrementDoVerbCount(this.name, 2);
// usually noun2 is indirect object
if (noun2.iov[this.name]) {
// if( !noun2.IOVdidDo(this.name)
if (
1 === noun2.IOVdidDoCount(this.name) &&
noun2.iov[this.name].on_first_success
) {
results = A.getSAF.call(
this.game,
noun2.iov[this.name].on_first_success,
noun2
);
} else {
results = A.getSAF.call(
this.game,
noun2.iov[this.name].on_success,
noun2
);
}
if (results && "string" === typeof results) msg += results;
// then disable?
if (noun2.iov[this.name].then_disable) {
noun2.iov[this.name].enabled = false;
}
// destroy after using?
results = noun2.tryDestroyIndirectObjectAfterUsing(this.name);
if (results.destroy)
msg += results.msg
? results.msg
: `${noun2.Articlename} crumbles to pieces. `;
}
// but some verbs, like attach, treat noun1 && noun2 as direct
else if (noun2.dov[this.name]) {
// if( !noun2.DOVdidDo(this.name)
if (
1 === noun2.DOVdidDoCount(this.name) &&
noun2.dov[this.name].on_first_success
) {
results = A.getSAF.call(
this.game,
noun2.dov[this.name].on_first_success,
noun2
);
} else {
results = A.getSAF.call(
this.game,
noun2.dov[this.name].on_success,
noun2
);
}
if (results && "string" === typeof results) msg += results;
// then disable?
if (noun2.dov[this.name].then_disable) {
noun2.dov[this.name].enabled = false;
}
// destroy after using?
results = noun2.tryDestroyDirectObjectAfterUsing(this.name);
if (results.destroy)
msg += results.msg
? results.msg
: `${noun2.Articlename} crumbles to pieces. `;
} else {
// warn because we probably shouldn't have got here without this
this.game.log(
"warn",
0,
`${noun2.id} is not indirect or direct subscribed to ${this.name}`,
"Verb"
);
}
} // noun2
if (noun3) {
noun3.incrementDoVerbCount(this.name, 3);
if (noun3.iov[this.name]) {
// if( !noun3.IOVdidDo(this.name)
if (
1 === noun3.IOVdidDoCount(this.name) &&
noun3.iov[this.name].on_first_success
) {
results = A.getSAF.call(
this.game,
noun3.iov[this.name].on_first_success,
noun3
);
} else {
results = A.getSAF.call(
this.game,
noun3.iov[this.name].on_success,
noun3
);
}
if (results && "string" === typeof results) msg += results;
// then disable?
if (noun3.iov[this.name].then_disable) {
noun3.iov[this.name].enabled = false;
}
// destroy after using?
results = noun3.tryDestroyIndirectObjectAfterUsing(this.name);
if (results.destroy)
msg += results.msg
? results.msg
: `${noun3.Articlename} crumbles to pieces. `;
} else {
// warn because we probably shouldn't have got here without this
this.game.log(
"warn",
0,
`${noun3.id} is not indirect subscribed to ${this.name}`,
"Verb"
);
}
} // noun3
if (msg) this.game.print(msg, this.game.getInput().output_class);
} // handleSuccess
/**
* Verb can be intransitive if it doesn't require a noun.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#canBeIntransitive
*/
canBeIntransitive() {
return !this.phrase1.requires_noun;
}
/**
* Does this verb have state or unstate?
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#hasState
*/
hasState() {
return this.state || this.unstate;
}
/**
* Get this verb's state or unstate.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#getState
*/
getState() {
return this.state || this.unstate;
}
/**
* Apply this verb's state or unstate to an asset.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#setState
*/
setState(asset, bool) {
if (this.getState()) asset.setState(this.getState(), bool);
}
/**
* Connect two assets that share a connection when acted upon by this verb.
* For example, in the case of 'plug computer into socket',
* each asset has the other asset's ID saved to its verb subscription like this:
* <br><br>
* <code class="property">computer.dov.plugIn.with_params.connections = ['socket']</code>
* <br>
* <code class="property">socket.iov.plugIn.with_params.connections = ['computer']</code>
* <br><br>
* This is one of two verb subscription properties that are related and very similar,
* and it's important to understand the distinction between them.
* <code class="property">...with_assets</code>
* defines which assets <strong>CAN BE</strong> connected.
* <code class="property">...with_params.connections</code>
* stores which assets <strong>ARE</strong> connected.
* <br><br>
* <strong>with_assets</strong>:
* <code class="property">computer.dov.plugIn.with_assets = ['socket']</code>
* <br>
* <strong>connections</strong>:
* <code class="property">computer.dov.plugIn.with_params.connections = ['socket']</code>
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#setVerbSubscriptionConnection
*/
setVerbSubscriptionConnection(direct_object, indirect_object) {
//console.warn(this.name,'setVerbSubscriptionConnection',direct_object.id,indirect_object.id);
if (
direct_object &&
direct_object.dov[this.name] &&
direct_object.dov[this.name].with_params.connections
) {
// some verbs allow direct objects to connect to nothing
let indirect_value =
indirect_object && indirect_object.id ? indirect_object.id : null;
if (
-1 ===
direct_object.dov[this.name].with_params.connections.indexOf(
indirect_value
)
) {
direct_object.dov[this.name].with_params.connections.push(
indirect_value
);
}
}
if (
indirect_object &&
indirect_object.iov[this.name] &&
indirect_object.iov[this.name].with_params.connections
) {
// I don't think indirect objects can connect to nothing, but for completionism
let direct_value =
direct_object && direct_object.id ? direct_object.id : null;
if (
-1 ===
indirect_object.iov[this.name].with_params.connections.indexOf(
direct_value
)
) {
indirect_object.iov[this.name].with_params.connections.push(
direct_value
);
}
}
}
/**
* Disconnect two assets that share a connection when acted upon by this verb.
* For example, in the case of 'plug computer into socket',
* each asset has the other asset's ID saved to its verb subscription like this:
* <br><br>
* <code class="property">computer.dov.plugIn.with_params.connections = ['socket']</code>
* <br>
* <code class="property">socket.iov.plugIn.with_params.connections = ['computer']</code>
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#unsetVerbSubscriptionConnection
*/
unsetVerbSubscriptionConnection(direct_object, indirect_object) {
if (
direct_object &&
direct_object.dov[this.name] &&
direct_object.dov[this.name].with_params.connections
) {
// some verbs allow direct objects to connect to nothing
let indirect_value =
indirect_object && indirect_object.id ? indirect_object.id : null;
if (
-1 !==
direct_object.dov[this.name].with_params.connections.indexOf(
indirect_value
)
) {
direct_object.dov[this.name].with_params.connections =
direct_object.dov[this.name].with_params.connections.filter(
(item) => item !== indirect_value
);
}
}
if (
indirect_object &&
indirect_object.iov[this.name] &&
indirect_object.iov[this.name].with_params.connections
) {
// I don't think indirect objects can connect to nothing, but for completionism
let direct_value =
direct_object && direct_object.id ? direct_object.id : null;
if (
-1 !==
indirect_object.iov[this.name].with_params.connections.indexOf(
direct_value
)
) {
indirect_object.iov[this.name].with_params.connections =
indirect_object.iov[this.name].with_params.connections.filter(
(item) => item !== direct_value
);
}
}
}
/**
* Test whether two assets are connected by this verb, for example
* a rope tied to a tree, or a computer plugged into a socket.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#hasVerbSubscriptionConnection
*/
hasVerbSubscriptionConnection(direct_object, indirect_object) {
let dconnect = true;
let iconnect = true;
if (
!direct_object.dov[this.name] ||
!direct_object.dov[this.name].with_params.connections ||
-1 ===
direct_object.dov[this.name].with_params.connections.indexOf(
indirect_object.id
)
) {
dconnect = false;
}
if (
!indirect_object.iov[this.name] ||
!indirect_object.iov[this.name].with_params.connections ||
-1 ===
indirect_object.iov[this.name].with_params.connections.indexOf(
direct_object.id
)
) {
iconnect = false;
}
// we should never have a one-way connection, and if we do, something is broken
if (dconnect && !iconnect) {
this.game.log(
"warn",
"high",
`${direct_object.id}.dov.${this.name}.with_params.connections contains ${indirect_object.id} but ${indirect_object.id}.iov${this.name}.with_params.connections does not contain ${direct_object.id}`,
"Verb"
);
return false;
}
if (!dconnect && iconnect) {
this.game.log(
"warn",
"high",
`${indirect_object.id}.iov.${this.name}.with_params.connections contains ${direct_object.id} but ${direct_object.id}.dov${this.name}.with_params.connections does not contain ${indirect_object.id}`,
"Verb"
);
return false;
}
return true;
}
/* *
* Test whether player can reach an asset from position on nest asset.
* @memberOf adventurejs.Verb
* @method adventurejs.Verb#canPlayerGoThereFromNest
*/
canPlayerGoThereFromNest(direct_preposition, direct_object) {
let response = { failure: false, return: null, msg: "" };
let input = this.game.getInput();
let player = this.game.getPlayer();
let nest_asset = player.getNestAsset();
let nest_preposition = player.getNestPreposition();
let reachable = true; // default = reachable
if (this.game.settings.xz_determines_reachability) {
// is distance between assets greater than jump_length?
let distance = A.getHorizontalDistance(
nest_asset.position,
direct_object.position
);
reachable = distance <= this.game.settings.jump_length;
// @TODO doSuccess handling for this
}
if (!reachable) {
this.game.debug(
`F1027 | ${this.name}.js | ${player.id} is nested ${nest_preposition} ${nest_asset.id} `
);
response.msg += `$(We) can't ${this.name} ${direct_preposition} ${
direct_object.articlename
} while ${player.getPostureGerund()} ${nest_preposition} ${
nest_asset.articlename
}. `;
response.failure = true;
return response;
}
return response;
}
} // class Verb
adventurejs.Verb = Verb;
})();