Pre-release
Adventure.js Docs Downloads
Score: 0 Moves: 0
// 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>
  */
  function Verb(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;

   /**
    * String provided in Verb definition file (aka preverb). 
    * Compound verb names have underscores instead of spaces, 
    * such as ask_about and climb_down.
	  * @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_about 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_relative_direction
    * @default false
    */
    this.is_relative_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 = {};
 
   
    return this;
  }
  var p = Verb.prototype;



  /**
   * 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
   * 
   */
  p.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
   * 
   */
  p.override_verb_success_msg;

  p.name;
  p.prettyname;
  p.past_tense;
  p.adjectives;
  p.is_direction;
  p.is_compass_direction;
  p.is_relative_direction;
  p.article;
  p.accepts_structures;
  p.accepts_adverb;
  p.accepts_adverbs;
  p.input_substitutions;


 /**
  * Return uppercase name of the verb.
  * @var {Getter} adventurejs.Verb#Name
  * @default []
  */
 Object.defineProperty(p, "Name", 
 { get: function() { return A.propercase(this.name); }, });

 /**
  * Returns "try[Verb]This" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#tryThis
  */
 Object.defineProperty(p, "tryThis", 
 { get: function() { return "try" + this.Name + "This"; }, });

 /**
  * Returns "try[Verb]WithThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#tryWithThis
  */
 Object.defineProperty(p, "tryWithThis", 
 { get: function() { return "try" + this.Name + "WithThis"; }, });

 /**
  * Returns "try[Verb]FromThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#tryFromThis
  */
 Object.defineProperty(p, "tryFromThis", 
 { get: function() { return "try" + this.Name + "FromThis"; }, });

 /**
  * Returns "try[Verb]ThisWithThat" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#tryThisWithThat
  */
 Object.defineProperty(p, "tryThisWithThat", 
 { get: function() { return "try" + this.Name + "ThisWithThat"; }, });

 /**
  * Returns "try[Verb]ThatWithThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#tryThatWithThis
  */
 Object.defineProperty(p, "tryThatWithThis", 
 { get: function() { return "try" + this.Name + "ThatWithThis"; }, });

 /**
  * Returns "try[Verb]ThisFromThat" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#tryThisFromThat
  */
 Object.defineProperty(p, "tryThisFromThat", 
 { get: function() { return "try" + this.Name + "ThisFromThat"; }, });

 /**
  * Returns "try[Verb]ThatFromThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#tryThatFromThis
  */
 Object.defineProperty(p, "tryThatFromThis", 
 { get: function() { return "try" + this.Name + "ThatFromThis"; }, });



 /**
  * Returns "do[Verb]This" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#doThis
  */
 Object.defineProperty(p, "doThis", 
 { get: function() { return "do" + this.Name + "This"; }, });

 /**
  * Returns "do[Verb]FromThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#doFromThis
  */
 Object.defineProperty(p, "doFromThis", 
 { get: function() { return "do" + this.Name + "FromThis"; }, });

 /**
  * Returns "do[Verb]WithThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#doWithThis
  */
 Object.defineProperty(p, "doWithThis", 
 { get: function() { return "do" + this.Name + "WithThis"; }, });

 /**
  * Returns "do[Verb]ThisWithThat" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#doThisWithThat
  */
 Object.defineProperty(p, "doThisWithThat", 
 { get: function() { return "do" + this.Name + "ThisWithThat"; }, });

 /**
  * Returns "do[Verb]ThatWithThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#doThatWithThis
  */
 Object.defineProperty(p, "doThatWithThis", 
 { get: function() { return "do" + this.Name + "ThatWithThis"; }, });

 /**
  * Returns "do[Verb]ThisFromThat" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#doThisFromThat
  */
 Object.defineProperty(p, "doThisFromThat", 
 { get: function() { return "do" + this.Name + "ThisFromThat"; }, });

 /**
  * Returns "do[Verb]ThatFromThis" for consistency with callAction()
  * @var {Getter} adventurejs.Verb#doThatFromThis
  */
 Object.defineProperty(p, "doThatFromThis", 
 { get: function() { 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 []
  */
	Object.defineProperty(p, "synonyms", 
  {
		get: function() 
    {
			return this.__synonyms;
		},
		set: function( synonyms ) 
    {

      // don't know how to initialize Object.defineProperty as an array
      if( false === Array.isArray( this.__synonyms ) )
      {
        this.__synonyms = [];
      }
  
      // apply new words
      this.__synonyms = synonyms;

      // 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 []
   */
	Object.defineProperty(p, "verb_prep_noun_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_prep_noun_prep_noun;
		},
		set: function( verb_prep_noun_prep_noun ) 
    {
      // don't know how to initialize Object.defineProperty as an array
      if( false === 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( verb_prep_noun_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_prep_noun_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_prep_noun_prep_nouns.push( [ verb_prep_noun_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_prep_noun_prep_noun = verb_prep_noun_prep_noun;      
    }
	});

  /**
   * 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 []
   */
	Object.defineProperty(p, "verb_noun_prep", 
  {
		get: function() 
    {
			return this.__verb_noun_prep;
		},
		set: function( verb_noun_prep ) 
    {
      // 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( verb_noun_prep.length > 0 ) 
      {
        for(var i = 0; i < verb_noun_prep.length; i++ ) 
        {
          this.dictionary.verb_noun_preps.push( [ verb_noun_prep[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_noun_prep = verb_noun_prep;
      
    }
	});

  /**
   * 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 []
   */
	Object.defineProperty(p, "verb_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_prep_noun
		},
		set: function( verb_prep_noun ) 
    {
      // 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( verb_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_prep_nouns.push( [ verb_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_prep_noun = verb_prep_noun;
      
    }
	});

  /**
   * For compound preps separated by spaces, verb/prep/prep, 
   * such as "get out of"
   * @var {Array} adventurejs.Verb#verb_prep_prep_noun
   * @default []
   */
	Object.defineProperty(p, "verb_prep_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_prep_prep_noun
		},
		set: function( verb_prep_prep_noun ) 
    {
      // 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( verb_prep_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_prep_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_prep_prep_nouns.push( [ verb_prep_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_prep_prep_noun = verb_prep_prep_noun;
      
    }
	});

  /**
   * 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 []
   */
	Object.defineProperty(p, "verb_prep_prep_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_prep_prep_prep_noun
		},
		set: function( verb_prep_prep_prep_noun ) 
    {
      // 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( verb_prep_prep_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_prep_prep_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_prep_prep_prep_nouns.push( [ verb_prep_prep_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_prep_prep_prep_noun = verb_prep_prep_prep_noun;

    }
	});

  /**
   * 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 []
   */
	Object.defineProperty(p, "verb_noun_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_noun_prep;
		},
		set: function( verb_noun_prep_noun ) 
    {
      // 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( verb_noun_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_noun_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_noun_prep_nouns.push( [ verb_noun_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_noun_prep_noun = verb_noun_prep_noun;
      
    }
    // verb_noun_prep_noun
	});

  /**
   * 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 []
   */
	Object.defineProperty(p, "verb_noun_prep_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_noun_prep;
		},
		set: function( verb_noun_prep_prep_noun ) 
    {
      // 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( verb_noun_prep_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_noun_prep_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_noun_prep_prep_nouns.push( [ verb_noun_prep_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_noun_prep_prep_noun = verb_noun_prep_prep_noun;
      
    }
    // verb_noun_prep_prep_noun
	});


  /**
   * 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 []
   */
	Object.defineProperty(p, "verb_noun_prep_noun_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_noun_prep_noun_prep_noun;
		},
		set: function( verb_noun_prep_noun_prep_noun ) 
    {
      // 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( verb_noun_prep_noun_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_noun_prep_noun_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_noun_prep_noun_prep_nouns.push( [ verb_noun_prep_noun_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_noun_prep_noun_prep_noun = verb_noun_prep_noun_prep_noun;
      
    }
	});


  /**
   * 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 []
   */
	Object.defineProperty(p, "verb_prep_noun_prep_noun_prep_noun", 
  {
		get: function() 
    {
			return this.__verb_prep_noun_prep_noun_prep_noun;
		},
		set: function( verb_prep_noun_prep_noun_prep_noun ) 
    {

      // 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( verb_prep_noun_prep_noun_prep_noun.length > 0 ) 
      {
        for(var i = 0; i < verb_prep_noun_prep_noun_prep_noun.length; i++ ) 
        {
          this.dictionary.verb_prep_noun_prep_noun_prep_nouns.push( [ verb_prep_noun_prep_noun_prep_noun[i], this.name ] );
        }
      }

      // apply new words
      this.__verb_prep_noun_prep_noun_prep_noun = verb_prep_noun_prep_noun_prep_noun;
      
    }
	});


  p.let_verb_handle_disambiguation;
  p.let_verb_handle_remaining_input;
  p.in_can_mean_on;
  p.dictionary = {};
  p.game = {};
  p.player_must_be = {};
  p.phrase1 = {};
  p.phrase2 = {};
  p.phrase3 = {};
  p.msgNoObject = null;
  p.msgNoAspect = null;
  p.state;
  p.unstate;
  p.state_strings;
  p.with_params = {};
  p.linked_params = [];

  /**
   * <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
   */
  p.related = [];

  /**
   * <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
	*/
  p.do = function Verb_do() 
  {
    this.game.log( "log", "high", "Verb.js > do " + this.name , 'Verb' );

    var input = this.game.getInput();
    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;
    }
    //if(qualifiedCount > 1) {
      //this.game.printInput();
    //}

    //if( 1 === qualifiedCount 
    //  && ( params.parsedNoun1.isPlural || params.parsedNoun1.isGroup ) 
    //) {
      //this.game.print("There's only one, but...");
    //}

    for( var i = 0; i < qualifiedCount; i++ ) 
    {
      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 > 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 > 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
 */
 p.doTry = function Verb_doTry() 
  {
    return true;
  }


 /**
  * <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
  */
  p.doSuccess = function Verb_doSuccess() 
  {    
    return true;
  }


  p.tryPhaseHook = function Verb_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
  * @returns {Object}
  */
  p.tryToInferIndirectObject = function Verb_tryToInferIndirectObject( direct_object ) 
  {
    if( !this.game.settings.try_to_infer_indirect_objects
      || !direct_object 
      || !(direct_object instanceof adventurejs.Matter) )
    {
      return { fail: true };
    }
    var related_verbs = this.related;
    related_verbs.push(this.name);

    var player = this.game.getPlayer();
    var found;

    // 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 ) )
    {
      return {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 ) return { prompt: true };

    if( found.length > 1 
      && this.game.settings.if_inferred_multiple_indirect_objects_then_prompt )
      {
        return {prompt:true};
      }

    return { success:true, indirect_object:found[0] };
  }



  /**
   * <strong>tryPlaceAssetInAspectOfAsset</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#tryPlaceAssetInAspectOfAsset
   * @param {Object} direct_object
   * @param {String} preposition
   * @param {Object} indirect_object
   * @returns {Object}
   */
  p.tryPlaceAssetInAspectOfAsset = function Verb_tryToPlaceAssetInAspectOfAsset( 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) ) 
    {
      msg = "$(debug:F1670 | Verb.js via "+this.name+".js | received bad request )";
      if( !indirect_object.hasAspectAt(preposition) )
      {
        msg += "$(We) can't push anything "
          + preposition
          + " "
          + indirect_object.articlename 
          + ". ";
      }
      else 
      {
       msg += this.game.parser.setUnparsedMessage( 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
      msg = "$(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.can_put[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)
      {
        msg = "$(debug:F1661 | Verb.js via "+this.name+".js | "+direct_object.id+".class "+direct_object.class+" is not among "+indirect_object.id+".can_put."+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.can_put[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)
      {
        msg = "$(debug:F1662 | Verb.js via "+this.name+".js | "+direct_object.id+" is not among "+indirect_object.id+".can_put."+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.measurements.size > asset_aspect.maxsize )
    {
      msg = "$(debug:F1667 | Verb.js via "+this.name+".js | " + direct_object.id + ".measurements.size > " + indirect_object.id + ".can_put[" + 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.measurements.weight > asset_aspect.maxweight )
    {
      msg = "$(debug:F1668 | Verb.js via "+this.name+".js | " + direct_object.id + ".measurements.weight > " + indirect_object.id + ".can_put[" + 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 )
    {
      msg = "$(debug:F1669 | Verb.js via "+this.name+".js | " + indirect_object.id + ".can_put[" + preposition + "].contents.length >= " + indirect_object.id + ".can_put[" + 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
  */
  p.validate = function Verb_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?
  */
  p.initialize = function Verb_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
  */
  p.enqueueCollection = function Verb_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
	*/
	p.set = function Verb_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
  */
  p.handleFailure = function Verb_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 = this.game.getSAF(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 = this.game.getSAF( noun1.dov[this.name].on_first_failure, noun1 );          
      }
      else
      {
        results = this.game.getSAF( 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 = this.game.getSAF( noun2.iov[this.name].on_first_failure, noun2 );
      }
      else
      {
        results = this.game.getSAF( 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 = this.game.getSAF( noun2.dov[this.name].on_first_failure, noun2 );
      }
      else
      {
        results = this.game.getSAF( 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 = this.game.getSAF( noun3.iov[this.name].on_first_failure, noun3 );
      }
      else
      {
        results = this.game.getSAF( 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
  */
  p.handleSuccess = function Verb_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 = this.game.getSAF(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 = this.game.getSAF( noun1.dov[this.name].on_first_success, noun1 );          
        }
        else
        {
          results = this.game.getSAF( 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 = this.game.getSAF( noun2.iov[this.name].on_first_success, noun2 );          
        }
        else
        {
          results = this.game.getSAF( 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 = this.game.getSAF( noun2.dov[this.name].on_first_success, noun2 );          
        }
        else
        {
          results = this.game.getSAF( 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 = this.game.getSAF( noun3.iov[this.name].on_first_success, noun3 );
        }
        else
        {
          results = this.game.getSAF( 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
  */
 p.canBeIntransitive = function Verb_canBeIntransitive() 
 {
  return !this.phrase1.requires_noun;
 }


 /**
  * Does this verb have state or unstate?
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#hasState
  */
 p.hasState = function Verb_hasState() 
 {
  return (this.state||this.unstate);
 }

 /**
  * Get this verb's state or unstate.
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#getState
  */
 p.getState = function Verb_getState()
 {
  return (this.state||this.unstate);
 }

 /**
  * 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
  */
 p.setVerbSubscriptionConnection = function Verb_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
  */
 p.unsetVerbSubscriptionConnection = function Verb_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
  */
 p.hasVerbSubscriptionConnection = function Verb_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;

 }

 /* *
  * Set a verb param for an direct object asset. 
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#setDirectParam
  */
//  p.setDirectParam = function Verb_setDirectParam( direct_object, param, value )
//  {
//   direct_object.dov[this.name].with_params[param] = value;
//  }

 /* *
  * Set a verb param for an indirect object asset. 
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#setIndirectParam
  */
//  p.setIndirectParam = function Verb_setIndirectParam( indirect_object, param, value )
//  {
//   indirect_object.iov[this.name].with_params[param] = value;
//  }

 /* *
  * Get a list of direct object verb param connections, if they exist.
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#getDirectVerbParamConnections
  */
//  p.getDirectVerbParamConnections = function Verb_getDirectVerbParamConnections( object )
//  {
//   if( !object.dov[this.name] 
//     || !object.dov[this.name].with_params.connections )
//     {
//       return [];
//     }

//   return object.dov[this.name].with_params.connections;
//  }

 /* *
  * Get a list of indirect object verb param connections, if they exist.
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#getIndirectVerbParamConnections
  */
//  p.getIndirectVerbParamConnections = function Verb_getIndirectVerbParamConnections( object )
//  {
//   if( !object.iov[this.name] 
//     || !object.iov[this.name].with_params.connections )
//     {
//       return [];
//     }

//   return object.iov[this.name].with_params.connections;
//  }

 /* *
  * Get int representing direct object verb param max connections, if it exists.
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#getDirectVerbParamMaxConnections
  */
//  p.getDirectVerbParamMaxConnections = function Verb_getDirectVerbParamMaxConnections( object )
//  {
//   if( !object.dov[this.name] 
//     || !object.dov[this.name].with_params.max_connections )
//     {
//       return 0;
//     }

//   return object.dov[this.name].with_params.max_connections;
//  }

 /* *
  * Get int representing indirect object verb param max connections, if it exists.
  * @memberOf adventurejs.Verb
  * @method adventurejs.Verb#getIndirectVerbParamMaxConnections
  */
//  p.getIndirectVerbParamMaxConnections = function Verb_getIndirectVerbParamMaxConnections( object )
//  {
//   if( !object.iov[this.name] 
//     || !object.iov[this.name].with_params.max_connections )
//     {
//       return 0;
//     }

//   return object.iov[this.name].with_params.max_connections;
//  }


  adventurejs.Verb = Verb;

}());