A modular replacement for adv3's executeAction()

This is another case where this is something I need but I’m not sure how many other TADS3/adv3 authors will: modularExecuteAction github repo.

This is similar to my previous modular replacement for executeCommand(), but this time replacing executeAction().

In stock adv3 executeAction() is called after the verb is resolved but before nouns are resolved (mostly). So it has an Action instance and wraps a call to Action.resolveNouns() in a try/catch block, calling the action’s doAction() if there are no surprises. In the middle it does some bookkeeping (deciding on whether or not to create an savepoint for undo and a couple of checks for things to do when one actor is giving another actor orders).

The main gimmick of the new process is that (like modularExecuteCommand) it provides a class for exception handlers, allowing you to add bespoke exception handlers without having to touch the underlying method.

As an example, here’s the replacement code for the one catch block in the stock function:

// Replacement for the single stock exception handler.
eaRemapActionSignalHandler: EaExceptionHandler
        type = RemapActionSignal

        handle(ex, st) {
                ex.action_.setRemapped(st.action);
                st.action = ex.action_;
                return(eaRestart);
        }
;

The type is a the subclass of Exception the handler handles (in this case RemapActionSignal).

The handle() method is what actually handles the exception. The arguments are the exception instance (ex here) and a state object for the current executeAction() process (st here). The latter will contain the arguments to executeAction() as well as the ResolveResults instance used for noun resolution. More details on that below.

After the handle() method does whatever it’s going to do, its return value should be one of:

  • eaRestart — tells executeAction() to start over from the beginning. This is done by the example above to handle action remapping
  • eaContinue — tells executeAction() to continue processing after the exception. By default this means the exception will be re-thrown (to allow it to be handled by something outside of executeAction())
  • eaHandled — tells executeAction() to NOT re-throw the exception, finish normal execution, and return

And for completeness, the properties of EaExceptionHandler mentioned above are:

  • dstActor — the actor the action is directed at
  • dstActorPhrase — the phrase from the command referring to the actor the action is directed at
  • srcActor — the actor initiating the action
  • countsAsIssuerTurn — a boolean flag that’ll be true if the command being processed is an order being given by one actor to another and this is the originating actor’s action for the turn
  • action — the resolved Action instance
  • results — the BasicResolveResults instance created by executeAction() for noun resolution

The last one is created by executeAction(), the rest are its arguments.

Dunno how useful this will be to anyone else. I got here in the process of writing code for handling “complex” player-to-NPC instructions, like for example telling an actor to go get an object that’s currently out of scope. I’d previously been handling this via elaborate tweaking of Action.objInScope() but that felt like it was getting out of hand as the number of cases it was necessary (and the number of objects affected) increased.

I might end up going back and re-tweaking my personScope module (that handles replacing the stock “You see no alice here.” failure message with something like “Alice isn’t here right now.” or “You don’t know anyone named Alice.” for Person instances) to use this as well.

3 Likes