Stopping the turn counter in verify()/verifyAction()

Is there a way to stop/abort a turn in e.g. a verify() in an object’s dobjFor() or in a verb’s verifyAction()? Specifically to get the result to be like what you’d get if the player entered an unknown word, attempted to examine an object that’s not present, or that kind of thing.

Here’s the simple example:

#charset "us-ascii"
#include <adv3.h>
#include <en_us.h>

DefineIAction(Foozle)
        verifyAction() {
                defaultReport('Foozle?  That doesn\'t even make sense. ');
                exit;
        }
;
VerbRule(Foozle)
        'foozle'
        : FoozleAction
        verbPhrase = 'foozle/foozling'
;

startRoom:      Room 'Void'
        "This is a featureless void. "
        roomDaemon() {
                "<.p>The room daemon<<one of>>
                        twiddles its thumbs.
                <<or>>
                        shuffles its feet.
                <<or>>
                        immanentizes the eschaton.
                <<shuffled>> ";
        }
;

tree:  Thing 'tree' 'tree'
        "If a tree falls in the nil, can any actor eXamine it? "
        location = nil
; 

me:     Actor
        location = startRoom
;

versionInfo:    GameID
        name = 'sample'
        byline = 'nobody'
        authorEmail = 'nobody <foo@bar.com>'
        desc = '[This space intentionally left blank]'
        version = '1.0'
        IFID = '12345'
;
gameMain:       GameMainDef
        initialPlayerChar = me
;

The “game” implements a verb “foozle” that doesn’t do anything, and it also contains a tree that’s not in the starting room (or any other room).

If you type, for example x tree, you get:

>x tree
You see no tree here.

>

…the turn counter does not advance and the room daemon doesn’t run. On the other hand if you type foozle:

>foozle
Foozle?  That doesn't even make sense.

The room daemon twiddles its thumbs.

>

…the turn counter advances and the room daemon runs.

What I want is to do something in foozle’s verifyAction() to make it behave the way x tree on the non-present tree behaves.

There’s a few possibilities, but I think throw new TerminateCommandException() instead of exit will achieve what you’re looking for.

I’d actually just run into that after doing some source diving (is it even mentioned anywhere in the documentation? it isn’t in Learning TADS3, for example) but the places where it occurs in the adv3 source really don’t make it clear how it’s “supposed to” be used. As opposed to, for example, the multi-page discussion of the philosophical differences between verify() and check() and so on.

So in what circumstances would a verify() not want to throw a TerminateCommandException versus just doing an exit? By analogy, I can’t find any command-terminating parser behaviors that don’t behave more like TerminateCommandException versus verify() { exit; }: not incrementing the turn counter, not doing any further processing, and so on. So I’m at a loss as to why the documentation and examples consistently do the other thing (exit in verify).

I was wary about suggesting TerminateCommandException. It’s a ParserException, which, technically, is not what’s going on with your command. (The parser properly connects the user’s input to your Action object, which does not take a noun, unlike >X TREE.)

And I assumed you don’t mean for FOOZLE to be a SystemAction. That might be a better choice, depending on what FOOZLE is meant to do.

I went down the path of verifyAction() returning a VerifyResultList. That does abort the command, but the turn counts and the room daemon still runs.

The problem is in Action.doAction():

            /* run the before routine for the entire action */
            beforeActionMain();

            /* run the subclass-specific processing */
            doActionMain();

            /* run the after routine for the entire action */
            afterActionMain();

Simply returning early from doActionMain(), doesn’t skip afterActionMain(), and throwing an ExitSignal is caught down the doActionMain() call chain.

TerminateCommandException was the best fit I could make.

I think it’s a matter of game design philosophy.

If the user types something which doesn’t make sense (the parser can’t connect a noun to an object, i.e., >X TREE), no game time is consumed. It’s as though nothing happened (because nothing did).

If the user’s input parses, but the command can’t be completed for some reason of internal game logic (e.g., try to open a rug, try to push the sunlight, etc.) then game time is consumed.

There might be other, better reasons. It’s probably one of those things where it makes sense in enough situations for the distinction. Asking the coder to decide for each verify() circumstance was probably considered too onerous. (It sounds onerous to me.)

I do think verify() failures should be the exceptional case in game play, not the norm. Personally, I don’t sweat this too much.

Since I have no context on what FOOZLE is to represent in your game, I don’t have an opinion about what’s right or wrong here.

I will note that adv3Lite has an abort macro defined to throw an AbortActionSignal. It looks like it does what you’re asking for (although it too is used sparingly in the library).

It’s just a placeholder, because I’m more worried about what’s going on in the rest of the game than in the specific action: think of a scene in which the player has a limited number of turns in which to investigate a room for clues before an NPC arrives and interrupts them. If the player tries, for example, to open a desk drawer only to find it locked, then it makes perfect sense that that action would consume time and advance scripting daemons. On the other hand there are a number of “you can’t do that now” types of responses where it feels a little punitive to advance the clock every time the player tries them in this kind of situation. I’m also thinking of tracking specific puzzle-related actions that the player tries, to provide a “free” “you already tried that” failure message when they’ve previously attempted some action that advanced the clock the first time.

Anyway, after posting the question I’d gone looking through the source, backtracking from gameMain.cancelCmdLineOnFailure, which is the closest thing I can find to what I want in the documentation (in 13.2 Stopping Actions in Learning TADS3). I found TerminateCommandException, but the only documentation for it that I can find is in the comments in exec.t, where it occurs in a giant try/catch block in parseTokenLoop().

In terms of simple test cases it certainly does what I want, but since it appears to be a literally undocumented exception in the main parsing logic gives me some pause. There are a bunch of kinds of exceptions, and the parser/library provide a bunch of #define macros to make them easy to use: exit, exitAction, abortImplicit, and so on. And then there’s the exception that it looks like I want to use and there isn’t a macro to use it. If that makes sense.

Anyway, if there isn’t anything cleaner/documented that accomplishes the same thing then I’ll just roll with TerminateCommandException and hope there aren’t any lurking surprises.