A TADS3/adv3 module providing a mechanism to execute command strings as arbitrary actors

Here’s another smallish TADS3/adv3 module, this one that allows a game to execute arbitrary command strings as any actor: execCommandAs github repo.

Usage is simple. To execute >TAKE PEBBLE as alice:

     execCommandAs(alice, 'take pebble');

To check to see if the command would succeed without actually changing the game state:

     execCommandAs(alice, 'take pebble', true);

The return value is boolean true on success, nil otherwise.

In the first example the command will be tested and then “really” executed if the test succeeds. In the second example only the test will be run. The point being that execCommandAs() will never attempt to “really” execute a command it expects to fail. This is mostly to prevent weird stuff in the transcript—if Alice tries to take the pebble and the pebble is in the player’s inventory that will produce “You won’t let Alice have that.” without anything to indicate what Alice is trying to do or why the game just produced that message.

Internally this uses savepoint() and undo in a try/catch/finally block for command testing, and execCommandAs() always tests before execution. So standard disclaimers about the undo stack, performance, and so on apply.

Making this work requires mildly extensive tinkering with adv3’s command execution, so the this module requires the modularExecuteCommand module, the outputToggle module, and the new moreFailureReports module.

The modularExecuteCommand module is needed to correctly (for our purposes) handle command failure states that involve exceptions (like >X WAINSCOTTING if “wainscotting” is not part of any object’s vocabulary).

The moreFailureReports module patches places in adv3 where actions fail without marking their failure in the transcript (like >TAKE PEBBLE when the pebble is in another actor’s inventory).

This is another one of those weird things that I happen to need but I have no idea how much use it would be to anyone else. But it was involved enough handling the corner cases (that I have identified so far) so I figured it was worth putting out there in case anyone else could use it.

5 Likes

I had to add something similar for the Predator AI in I Am Prey. I wanted the NPC to be able to use the same action code as the player without needing to make a Predator-only approximation for everything. Really cool to see this for Adv3 as well.

2 Likes

Yeah, I’d also previously put up a different module (newActorCommand) around a year ago which kinda does the same thing, but doesn’t check for any of the special cases. This mostly works if you’re just doing NPC scripting using a list of known good commands (NPCs with regular schedules that they always follow in the same way, a la Majora’s Mask and so on). But once I started trying to do more elaborate, open-ended NPC behaviors all the additional checks became a lot more important.

3 Likes

Hmmm… You should start to integrate all your adv3 extensions together into a modular extension library.

Best regards from Italy,
dott. Piergiorgio.

1 Like

I should integrate them all into an actual game at some point as well.

5 Likes

If I have got the implicit correctly, should I note that perhaps can be useful balancing between top-down and bottom-up ?

Best regards from Italy,
dott. Piergiorgi.

1 Like

Or breadth-first versus depth-first.

But yeah, that’s kinda what I’m in the process of doing. I almost always start out with a messy prototype where I just kinda slap together all the moving parts I think I’ll need, and just plan for a complete re-factor after I get a feel for what’s a one-off and what’s a general use case.

Most of the stuff I’ve put up over the past couple months has been a refactor (or re-refactor) of code I’d originally hammered out for a “nothing but placeholder text” v0.0 version of a game I wrote like a year and a half ago.

1 Like

Glad to see I’m not alone in this process. :grin:

1 Like

Minor update:

Previously, executing a command via execCommandAs() would advance the actor’s nextRunTime by whatever the executed action’s actionTime is.

My assumption is that this is generally not the desired behavior—at least for my purposes I’m doing things like calling execCommandAs() in an AgendaItem, so the actor has already “paid” for a turn, meaning counting the actionTime would double (or worse) the turn cost of taking an action. This was producing maddeningly difficult to troubleshoot problems where NPC scripting would just pause periodically, compounded by a similar issue (also now fixed) in the thirdPersonAction module I put up the other day.

Anyway, now by default the actor’s nextRunTime is reset to whatever it was before doing a execCommandAs(). There’s a new optional fourth argument that, if true, does things the old way (that is, the actor’s nextRunTime will be advanced by the actionTime for the executed action).

2 Likes