Implicit Action Reports

by Eric Eve

Implicit Actions are actions carried out by a TADS 3 game that are not explicitly commanded by the player, usually in order to facilitate the command the player has just explicitly typed. For example if the player types EAST when going east would take the player character through a closed door, the game will (normally) first try to open the door with an implicit action to enable the explicitly commanded travel action to go ahead. Similarly, if the player types PUT BALL IN BOX when the ball is in plain view but not actually held by the player character, a TADS 3 game will typically trigger an implicit action to take the ball before carrying out the PUT IN action the player explicitly requested.

Such behaviour is generally desirable, since it relieves the player of the tedium of having to explicitly open every door before walking through it, or explicitly picking up every object before putting it somewhere (and so on). It also makes for a much smoother playing experience to get something like:

>EAST
(first opening the door)

Hall
This large hall looks as if a small army of servants spends 20 hours a day
polishing its floor. Doors lead off in all the cardinal directions.

You see a red ball and a cardboard box here.

>PUT BALL IN BOX
(first taking the red ball)
Done.

Rather than the potentially frustrating:

>EAST
You can't, because the door is in the way.

>PUT BALL IN BOX
You need to be holding the red ball before you can put it in anything.

Normally the library handles all this automatically for you, usually through the preconditions applied to various actions. It is also possible to trigger an implicit action in your own code using the tryImplicitAction macro. Either way the game will take care of reporting the implicit action for you, so that the player is kept informed of what action the game has just performed on his/her behalf. These implicit action reports generally take the form of the messages in brackets we have just seen in the examples above, e.g.

(first opening the door)

(first taking the red ball)

A slightly different form of implicit action report is displayed when an implicit action is attempted but fails for some reason (the door is locked or the ball is too heavy); in such a case the implicit action attempted is usually introduced with 'trying to'; for example:

>EAST
(first trying to open the door)
The door seems to be locked.

>PUT BALL IN BOX
(first trying to take the ball)
The ball must be made of solid lead - you can't shift it!

For the most part these standard implicit action reports work well, and you may never need to worry about them in your game. Occasionally, though, you may feel you want to vary them for stylistic effect, or to avoid a report that looks jarring. Suppose, for example, that your leading female NPC won't let the PC kiss her while he's carrying the large antique china vase, but you decide to handle putting down the vase through an implicit action:

  dobjFor(Kiss)
  {
	  verify() {}
	  action()
	  {
		  if(vase.isIn(gActor);
		     tryImplicitAction(Drop, vase);
		     
		  "You give Myrtle a firm kiss on her lips, which makes her burst
		   into a fit of giggles. ";
      }		      
  }

Then what the player will see is something like:

>kiss myrtle
(first dropping the priceless antique vase)
You give Myrtle a firm kiss on her lips, which makes her burst
into a fit of giggles.

And you may think this looks a little jarring. Perhaps you would have preferred:

>kiss myrtle
(first putting the priceless antique vase carefully down on the floor)
You give Myrtle a firm kiss on her lips, which makes her burst
into a fit of giggles.

This is not too difficult to do once you know how, but it may not be immediately apparent how to do it. This article will explore how.


The Parts of an Implicit Action Report

If you want to customise an implicit action report, it's first helpful to recognize that it's made out of a number of elements. For present purposes we may regard these as the report framework and the action phrases. The action phrases are the parts of the report referring to the specific actions being attempted, while the report framework is the surrounding material that identifies the whole thing as an implicit action report, and makes it grammatically complete. So for example, in the implicit action reports:

(first opening the door)
(first unlocking the door then opening it)
(first trying to open the door)

opening the door
unlocking the door
opening it
to open the door

Are all action phrases, whereas:

(first      )
(first        then     )
(first trying          )

Are all report framework. From this it can be seen that an action phrase always consists of a verb (as either a participle - e.g. opening - or as an infinitive - e.g. to open) plus the object or objects involved in the action (the door, or the door with the brass key if the implicit action were "first unlocking the door with the brass key"). It is likely to be more useful (and generally easier) to customise an action phrase than the report framework, so we shall spend most of the time looking at the former before giving a brief discussion of the latter.


Customizing Action Phrases

If all we want to do is to customize the action phrase part(s) of an implicit action report, then ideally we'd like to be able to leave everything else to the standard library rather than trying to reimplement wheels in our own code. In this context "everything else" refers to all aspects of the framework (such as words like "first", "trying" and "then") as well as deciding whether we need the participle or infinitive form ("first opening the door" vs "first trying to open the door") and using a pronoun instead of a noun the second and subsequent times we refer to the same object ("first unlocking the door then opening it" rather than "first opening the door then opening the door"). Fortunately it is easy to write code that changes the action phrase(s) while leaving the standard library to do everything else as normal.

The key to this is understanding where the action phrase comes from. In every case the action phrase of an implicit action report is derived from the verbPhrase property of the associated action, so all we need to do is to change that verbPhrase at some appropriate point, just before the standard libary code uses it to generate the implicit action report. In the standard library (and on custom actions you define in your own code), the verbPhrase property is generally defined in the appropriate VerbRule (if you're not familiar with this, see the article on How to Create Verbs). A verbPhrase typically looks like:

verbPhrase = 'drop/dropping (what)'

verbPhrase = 'unlock/unlocking (what) (with what)'

In these verbPhrases, (what) is a placeholder for the noun or nouns involved in the action, and the 'drop/dropping' or 'unlock/unlocking' give the infinitive (minus the 'to') and the participle of the corresponding verb. So, if the implicit action report routine wants to construct a message for an implicit action that succeeds it takes the verb form immediately after the slash (/) and substitutes the noun for (what), generating a message like 'dropping the priceless antique vase'. Similarly, if an implicit action fails, the implicit action report routine take the verb form immediately before the slash (e.g. unlock), inserts the noun or nouns into the (what) slots (e.g. 'the door with the brass key') and prepends "trying to" before the whole thing ("first trying to unlock the door with the brass key").

So, in the antique vase example, what we need to do is to change this verbPhrase to something like 'place/placing (what) gently on the floor' just before the implicit drop action report is generated. The best place to do this is probably in the vPhrase(obj) method of the appropriate action:

modify Drop  
    vPhrase(dobj)
    {
        if(dobj == vase)
            return 'put/putting (what) carefully down on the floor';
        
        return inherited(dobj);
    }    
;

The vPhrase(dobj method on a TAction obtains the verbPhrase to use when constructing the implicit action report to be used when the action is carried out implicitly. The dobj parameter is the direct object of the current action. On a TIAction vPhrase(dobj, iobj) is called with two parameters (the direct and indirect objects). In this example above we simply test whether the current direct object is the vase, and if so we substitute our custom verbPhrase for the standard one. Implicitly dropping anything other than the vase will result in the standard message being used.

This method is fine where there's only one object we want to treat as a special case, but if there were several - especially several with different customized messages - we might want a more a general solution (rather than writing a switch statement or multiple if tests in the getImplicitPhrase() method). What would be useful would be a method that had the object concerned decide whether to use a custom phrase, and if so to supply that custom it. We could achieve this like so:

modify Drop   
    vPhrase(dobj)
    {
        return dobj.verbPhraseDobjDrop  ?? inherited(dobj);      
    }    
;

This code checks whether the current direct object has a non-nil verbPhraseDobjDrop property, and, if (but only if) it has, changes the verbPhrase property on the current action to match. We could, of course, have called the property of the direct object we're testing for anything we liked, but there are a couple of advantages to the name suggested here. First, the suffix 'DobjDrop' makes it clear what this verbPhrase variant is for (which could become more relevant if we were customizing several actions in this way) and secondly it corresponds to the way the dobjFor propertyset expands property names. This means that we can then customize the vase thus:

+ vase: Thing 'priceless antique vase' 'priceless antique vase'
    dobjFor(Drop)
    {
        action()
        {
            if(!gAction.isImplicit)
              "{You/he} set{s} the antique vase carefully down on the ground. ";
            inherited;
        }
        verbPhrase = 'set/setting (what) carefully down on the ground. '
    }
;

Note that here we've also customized the response to an explicit DROP VASE so that it corresponds to the implicit message, and that we've included a test to ensure that this message is only shown when the action is not implicit (since otherwise we'd see both custom messages - the implicit and the explicit ones - when the vase was dropped implicitly, which would not look good.)

Note also that if you overrise vPhrase() along the lines of the previous example, but don't go on to define the corresponding property for that action on at least one game object, you'll get a compiler warning.

The technique outlined here is easily generalizable and should cater for most implicit action phrase tweaking you're likely to do, but there are a couple of further points to bear in mind:

Modifying Framework Text

Modifying the framework text for implicit action reports is probably less useful to do, but not particularly difficult to achieve, these fragments are all defined in BSMg strings:

So, if we wanted to, we could change all these fragments using a CustomMessages object:

CustomMessages
    [
        Msg(implicit action report start, '<b>[to begin with '),
        Msg(implicit action report separator, ' and next '),
        Msg(implicit action report terminator, ']</b>\n'),
        Msg(implicit action report failure, 'totally failing to ')
    ]
;

If we liked we could define this CustomMessages' active property to define when we wanted this set of fragments to be used, e.g. active = gActionIn(Take, Drop) or gPlayerChar = barnabas. (Whether you think any of thie is a good idea is, of course, another matter; the point is that it's not hard to do if you want to).

Silent Implicit Actions

It's normally a good idea to display implicit action reports so that players can see what's happened in the course of carrying out the command they asked for. Otherwise they might imagine the player character has walked through a closed door or wonder how they've ended up holding the widget they couldn't put in the locked box. but if you want to silence implicit action reports you can do so simply by setting reportImplicitActions to nil either on individual actions or globally on the Action class.

If you wanted to silence Implicit Action reports for a pariculer block of code, for example, you could use a pattern like the following:

   ...
   try
   { 
      Action.reportImplicitActions = nil;
      //Do stuff without implicit action reports here 
      ...
   }
   finally   
   {
      Action.reportImplicitActions = true;
   }