Special Verbs

Introduction

Consider the case where your game contains a single doorbell object implemented as a Button that can be pushed. The player can ring the bell by using the command PUSH BELL, but what if they type RING BELL? You probably don't want to modify VerbRule(Push) to make 'ring' a global synonym for 'push', since there would be too many situations in which this could incongrous, for example if the playher tried RING LEVER or RING AUNT MAUDE. On the other hand it would seem a lot of work just to cover this one case if to define a new Ring Action and VerbRule(Ring) and dobjFor(Ring) blocks on both Thing and the doorbell object. The SpecialVerb class allows you to solve this problem by letting you define:

SpecialVerb 'ring' 'push' @doorbell;

In brief, this means that a command beginning RING should be translated into one beginning PUSH when the direct object is doorbell. The attempt to ring anything else, such as the lever or Aunt Maude, will be met with the response "You can't ring that. "

If there's a second bell in the game that's rung by pulling a chain, then it's perfectly okay to define a second SpecialAction to cover that case too:

SpecialVerb 'ring' 'pull' @chainbell;

Provided doorbell and chainbell are never in scope at the same time, the game will be perfectly capable of working out which SpecialVerb to apply (which it has to do before the parser has resolved the objects involved in the command, since it first needs to know what the action is before it can choose appropriate objects for it). The game should normally be able to work out which SpecialVerb to apply even if both bells were in scope at the same time, provided the player's input unambiguously referred to one of them (e.g., RING DOOR BELL or RING CHAINED BELL), although in the event that the player just typed RING BELL, which could refer to either bell, it would arbitrarily choose one or other SpecialVerb, since it wouldn't have enough to go on to make the correct choice.

Suppose finally that somewhere in the game we have a bridge that the player might try to CROSS or WALK ACROSS or GO ACROSS (even though this could be achieved simply by issuing an ordinary travel commamnd like E or W):

orchard: Room 'Orchard'
    "This orchard is full of apple trees. A path leads back to the east. To
    the north lies a small hut. Over to the west a small wooden footbridge
    crosses a stream. "   
   
    west = meadow
	
	...
;

meadow: Room 'Meadow'
    "This huge meadow stretches as far as you can see. A small footbridge cross the stream
    running along its eastern boundary. "
    
    east = orchard
;

MultiLoc, Decoration 'stream'
    initialLocationList = [meadow, orchard]
;


footbridge: MultiLoc, Fixture 'footbridge; small wooden foot; bridge'   
    initialLocationList = [orchard, meadow]
;

Suppose this is the only object (or only one of a very few objects) that can be crossed in the game, and we'd rather not have to go to the trouble of define a Cross Action and a VerbRule(Cross) and default dobjFor(Cross) handling on Thing just to allow the player to type CROSS BRIDGE or WALK ACROSS BRIDGE. We could perhaps make footbridge a Passage and then define:

SpecialVerb 'cross|walk across|go aoross' 'go through' @footbridge;

And this would just about get the job done, except that GO THROUGH BRIDGE does not seem a natural way of phrasing a command to cross it, so player who try GO THROUH BRIDGE might find its equivalent to CROSS rather odd (GO THROUGH BRIDGE might more naturally mean going under it when navigating the stream). An alternative might be to make the bridge an Enterable and divert CROSS to ENTER, but again players who type ENTER BRIDGE probably wouldn't expect it to be equivalent to crossing it (ENTER BRIDGE more naturally suggests walking onto it and stopping, or going under it when navigating the stream). But then seem to be left without an existing action to divert CROSS to.

To meet this problem, we can divert CROSS into a SpecialAction by using the verb 'sp#act' (which players are unlikely to type at the keyboard unless they've read this manual and are going out of the way to break your game; but if try it their attempt will be rebuffed with "I really refuse to understand that command.").

We can then handle this case by using the following:

SpecialVerb 'cross|walk across|go over' 'sp#act' [footbridge];

footbridge: MultiLoc, Fixture 'footbridge; small wooden foot; bridge'   
    initialLocationList = [orchard, meadow]
	
    dobjFor(SpecialAction)
    {
        verify() {}
        action()
        {
            
            "You <<gAction.specialPhrase>> {the dobj}. ";
            
            local dest = gActor.isIn(orchard) ? meadow : orchard;          
            
            dest.travelVia(gActor);                
        }
    }	
;

There are a few points to note here:

This covers all or most of what you need to know about SpecialVerbs to use them in your own game. At that follows in this chapter is pretty much the small print.

Elaborations

So far we have used the SpecialVerb template without explaining what properties it refers to. The SpecialVerb template is defined as:

SpecialVerb template 'specVerb' 'stdVerb' @matchObjs | [matchObjs];

We are now in a position to list the properties and methods of SpecialVerb that are of direct relevance to game authors (the remainder are all for internal use by the library):

With the possible exception of objChecks() (and even then probably not often), you should only very nearly need to override any of these properties or methods (apart from the three defined via the template). Here is an example of how you might use objChecks() to rule out slamming a closed door and reporting that an open door has been slammed:

SpecialVerb 'slam' 'close' @Door
    objChecks(dobj, iobj, aobj)
    {
        if(!dobj.isOpen)
        {
            "It would surely be exceptionally petulant to attempt to <<verbPhrase>> <<dobj.theName>>
            when it isn't even open. ";
            abort;		
        }
		
        dobj.slammed = true;
    }
;

modify Door
    slammed = nil
	
    dobjFor(Close)
    {
        action()
        {
            if(slammed)
                "You slam {the dobj} shut with unreasonable force. ";
            
            /* Reset our slammed property. */
            slammed = nil;
            
            inherited();
        }
    }
;

If you're having to do too much tweaking of a SpecialVerb to make it do what you want, it may be that you'd be better off using a different means of getting the effect you're after (i.e., defining a new Action, or modifying an existing VerbRule, or using a Doer or replaceAction()). Using SpecialVerb should be fine for the kinds of cases we've illustrated here, but overusing them on too many objects risks producing code that's less robust than doing things the long way.

Finally, if you are having trouble with a SpecialVerb it may help to know a little about how the library handles them internally. Once a player has entered a command, the parser follows the following steps in relation to SpecialVerbs:

  1. After tokenizing the player's input and carrying out some preliminary processing with the tokens, the parser calls specialVerbMgr.matchSV(toks) to give it a chance to change the toks that are passed to it.
  2. The specialVerbMgr then looks for SpecialVerbs that match the toks passed to it.
  3. Assuming any SpecialVerbs are found to match, each matching SpecialVerb calculates a score based on whether any of the objects in its matchObjs property are in scope and how well any of these match the noun phrase entered by the player (these are by far the most dominant considerations) and then any conditions defined on its where, during, and when properties (if any). The priority is added to the final score but is likely to make a difference only in the event of what would otherwise be a tie.
  4. The matching SpecialVerbs are then sorted in descending order of score to allow the highest scoring one (the first entry in the sorted list) to be selected as the one to be used.
  5. The toks entered by the player that match the SpecialVerb's specVerb property are then replaced by those defined on its stdVerb property, and the resulting toks returned to Parser.parse() to continue on its way with the revised list of toks.If no matching SpecialVerbs were found, the list of toks will be returned to Parser.parse() unchanged.
  6. Just prior to invoking the Doer that will execute the command, the new Command object (which at this point knows the action and objects involved) calls the current SpecialVerb's checkSV() method, which will prevent the action from going ahead if the SpecialVerb's matchObjs doesn't match the direct object of the command, or any of its where, during, or when properties rule it out.
  7. Finally, the SpecialVerb's objChecks() is given a chance to intervene.