Interrupting an Implicit Action

Is there any way (using adv3) to interrupt an implicit action?

As always, here’s a simple example game to illustrate. The player starts out in a hammock, wearing glasses. When the player gets out of the hammock they stumble, dropping their glasses. The starting room is, for some reason, adjacent to a rickety rope bridge over a yawning chasm. If the player attempts to cross the bridge wearing their glasses they’re fine, but if they’re not wearing their glasses they miss the bridge and plummet to the bottom of the chasm.

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

startRoom:      Room 'Clearing'
        "This is a clearing containing a hammock.  A rickety rope
                bridge leads across a chasm to the north. "
        north = bridgeConnector
;
+ hammock: BasicBed, Fixture 'hammock' 'hammock'
        "It's a barely-implemented hammock. "

        defaultPosture = lying
        allowedPostures = [ lying ]
        obviousPostures = [ lying ]

        makeStandingUp() {
                inherited();
                if(glasses.wornBy == me) {
                        glasses.moveInto(startRoom);
                        glasses.wornBy = nil;
                        "You fumble your way out of the hammock and
                                stand up, dropping your glasses in the
                                process. ";
                } else {
                        "You fumble your way out of the hammock and stand up. ";
                }
        }
        cannotGoThatWay() {
                "You're not going anywhere until you get out of the hammock. ";
        }
;
+ bridgeConnector: ThroughPassage
        // Check to see if we're the player without their glasses
        isPlayerWithoutGlasses(obj) {
                if((obj == me) && (glasses.wornBy != me))
                        return(true);
                return(nil);
        }
        getApparentDestination(origin, traveler) {
                return(bridge);
        }
        getDestination(origin, traveler) {
                // if we're not in the starting room, nothing special happens
                if(origin != startRoom)
                        return(bridge);
                // if we're the player and we're not wearing our glasses, fall
                if(isPlayerWithoutGlasses(traveler))
                        return(chasm);
                // go onto the bridge
                return(bridge);
        }
        noteTraversal(traveler) {
                if(isPlayerWithoutGlasses(traveler)) {
                        "Stumbling around nearly blind, you miss the
                                rope bridge and fall into the chasm below. ";
                }
        }
;
chasm: OutdoorRoom 'The Bottom of a Chasm'
        "This is the bottom of a chasm.  The only exit is up. "
        up = startRoom
;
bridge: OutdoorRoom 'Rope Bridge'
        "This is a rickety rope bridge, connecting a small clearing to the
                south with nothing in particular to the north. "
        south = startRoom
;

me:     Actor
        location = hammock
        posture = lying
;
+ glasses: Wearable 'pair glasses' 'glasses'
        "They're a nondescript pair of glasses. "
        isPlural = true
        wornBy = me
;
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
;

Most of the time this all works fine, but if the player just types north at the start of the game, they’ll implicitly stand and move in a single turn, ending up at the bottom of the chasm before they’re even aware they’ve dropped their glasses.

What I’d like is some way to flag, in this case in the hammock’s makeStandingUp() method, that no further implicit actions should be taken this turn. Basically some way of declaring that something important has happened and the player should have to opportunity of re-evaluating things before any implicit actions are taken.

If this was happening in a dobjFor() then we have options like nonObvious and dangerous, but there’s no way (as far as I know) to inject that kind of thing into this sort of situation.

2 Likes

This was my first idea:

        makeStandingUp() {
                if (gAction.isImplicit)
                        abortImplicit;

Which results in:

Clearing (lying on the hammock)
This is a clearing containing a hammock.  A rickety rope bridge leads across a chasm to the north.

>n
(first standing up)
You must stand up before you can do that.

>stand
You fumble your way out of the hammock and stand up, dropping your glasses in the process.

>n
Stumbling around nearly blind, you miss the rope bridge and fall into the chasm below.

It mostly appeared to work, other than this annoyance:

>n
(first standing up)
You must stand up before you can do that.

I would prefer the implicit message was “(first trying to stand up)” or simply wasn’t printed.

So I dug around some more and found that this (without the above change) is better:

+ hammock: BasicBed, Fixture 'hammock' 'hammock'
        tryMakingTravelReady(conn) { return nil; }

The base class implementation is:

    tryMakingTravelReady(conn) { return tryImplicitAction(Stand); }

With this change, I get this result:

>n
You must stand up before you can do that.

>stand
You fumble your way out of the hammock and stand up, dropping your glasses in the process.

>n
Stumbling around nearly blind, you miss the rope bridge and fall into the chasm below.

The Bottom of a Chasm
This is the bottom of a chasm.  The only exit is up.

>undo
Taking back one turn: "n".

Clearing
This is a clearing containing a hammock.  A rickety rope bridge leads across a chasm to the north.

You see some glasses here.

>get glasses
Taken.

>n
Stumbling around nearly blind, you miss the rope bridge and fall into the chasm below.

The Bottom of a Chasm
This is the bottom of a chasm.  The only exit is up.

>undo
Taking back one turn: "n".

Clearing
This is a clearing containing a hammock.  A rickety rope bridge leads across a chasm to the north.

>wear glasses
Okay, you're now wearing the glasses.

>n
Rope Bridge
This is a rickety rope bridge, connecting a small clearing to the south with nothing in particular to the north.

I think that’s more what you’re looking for.

The moral, I think, is to use abortImplicit to cancel implicit actions, unless there’s a way to selectively prevent the implicit action from being initiated.

2 Likes

I believe the solution you may be looking for is the isActorTravelReady method of the room in which the directional connectors are defined. The default is return gActor.posture==standing. If the method is empty the actorTravelReady Precondition object will not enforce anything, or of course you could make the method conditional.
I’ve done no tests but perhaps you could use a nestedAction instead of an implicit action if the transcript isn’t showing how you’d like?

1 Like

noteTraversal could also throw a failCheck if you started the turn with the glasses on and the pc isn’t wearing them during the execution of the method ( after implicits have run)

1 Like

It looks like abortImplicit is in fact what I’m looking for, although I think I need some additional fiddling around; I’m concerned about having to think up every possible situation in which I might have to twiddle things like tryMakingTravelReady() in this example.

Basically in the actual code I’m working on I have potentially many circumstances in which the player might be doing something and would want to abort any implicit action sequence in the middle due to the game state changing. Think of something like a murder mystery in which you’re investigating the various NPCs as suspects, and you’re surreptitiously looking for clues when one of the suspects enters the room. Or if they’re pacing from one end of the room to the other and you’re trying to palm an item when they’re not looking. Whatever.

Anyway, what I think I’d like to do is something like:

        makeStandingUp() {
                if(gAction.isImplicit)
                        abortImplicitSilent;

…where I’ve elsewhere defined something like…

myGameConfig: object
        throwAbortImplicitSilent() {
                gTranscript.clearReports();
                throw new AbortImplicitSignal();
        }
;
#define abortImplicitSilent (myGameConfig.throwAbortImplicitSilent)

Which works, but I don’t actually want that gTranscript.clearReports() in there. Looking through the “Manipulating the Transcript” documentation and the source for CommandTranscript.summarizeAction() it looks kinda like this ought to work:

        throwAbortImplicitSilent() {
                gTranscript.summarizeAction(
                        { x: x.ofKind(ImplicitActionAnnouncement) },
                        { vec: '' }
                );
                throw new AbortImplicitSignal();
        }

…to just remove the implicit action reports, but it doesn’t. Indicating that there’s something about how/when reports are added that I’m missing. Doing a bit of debugging via printf() (or the TADS3 equivalent) it looks like the transcript is empty when the AbortImplicitSignal is thrown, in that:

        throwAbortImplicitSilent() {
                gTranscript.summarizeAction(
                        { x: true },
                        { vec: '[This space intentionally left blank] ' }
                );
                throw new AbortImplicitSignal();
        }

…produces a result apparently identical to the default report (that is, printing the stock “(first standing up)” implicit action report). Which makes me somewhat puzzled that my first example in this post, using gTranscript.clearReports() works (for some definition of “work”).

Is implicit stand the only thing you’re trying to control, or any range of implicits like opening containers and getting out of nested rooms?

The latter. In the code I’m working on, I’m trying to create a fairly dynamic game world where the game state can change independent of player input (due to things like NPCs moving around on schedules/agendas) and want to minimize the number of times players get surprised by something that catches them because of implicit actions.

1 Like

While poking around in the Adv3 source trying to figure out a transcript-rewriting solution, I think I stumbled onto a very simple approach.

The first bit is a slight revision to makeStandingUp() so that we only fiddle with things if we’ve just dropped our glasses in an implicit action:

        makeStandingUp() {
                inherited();
                if(glasses.wornBy == me) {
                        glasses.moveInto(startRoom);
                        glasses.wornBy = nil;
                        "You fumble your way out of the hammock and
                                stand up, dropping your glasses in the
                                process. ";
                        if(gAction.isImplicit) {
                                "Suddenly finding yourself nearly blind, you
                                immediately stop what you're doing. ";
                                interruptImplicit;
                        }
                } else {
                        "You fumble your way out of the hammock and stand up. ";
                }
        }

The new interruptImplict is simply:

#define interruptImplicit (myInterruptHandler.interruptImplicitAction)

myInterruptHandler: object
        interruptImplicitAction() {
                gAction.callAfterActionMain(self);
        }
        afterActionMain() {
                exit;
        }
;

This works well enough for this test case at least. I’m not familiar enough with the interpreter’s parsing lifecycle to know if there’s any hidden gotchas here (like something that might be happening after all the registered afterActionMain()s are called, possible race conditions with multiple afterActionMain()s registered on the action, and so on.

Anyway, this sidesteps all of problems with rewriting the parenthetical implicit action messages because we’re not preventing any actions that would make them sound inconsistent:

>n
(first standing up)
You fumble your way out of the hammock and stand up, dropping your glasses in
the process.  Suddenly finding yourself nearly blind, you immediately stop what
you're doing.

>

The main caveat is that this really requires additional text to explain that the implicit action was interrupted, but at least in this example that’s easy enough to tuck into the conditional that’s calling the interrupt anyway.

3 Likes

I believe this has everything you need to know about “trying” implicit actions:

http://tads.org/t3doc/doc/techman/t3imp_action.htm

1 Like

I have recently done a similiar thing in one of my WIP (with a small, but marked pair omissis for spoilery reasons):

 actorAction()
  {
	if(newcome && gActionIs(Stand)) {
	  switch (++standAttempts)
		{case 1:
		  failCheck('You try to stand up, but your movement is so awkward 
			that you fail to rise up.');
		  break;
		case 2:
		  failCheck('You try again to stand up, using arms and legs, but 
			you footing are unstable and fall on your back.');
		  break;
		case 3:
		  "At the third attempt,You somewhat manage to finally stand up; 
		  your body seems so awkward and the footing unstable, like [OMISSIS]";
		  newcome = nil;
[OMISSIS] =1; // var name potentially spoilery
		  break;
		default: "4th time,should NEVER happens";
		  newcome = nil;
		  } //case
		} //if

	} //actorAction
  standAttempts = 0
  newcome = true

This implement the PC starting the game on the floor, and can’t rise up (and doing actions requiring standing up) until the 3rd attempt.

Best regards from Italy,
dott. Piergiorgio.

Doing some light thread necromancy here.

Finally getting around to re-factoring the game code that I needed this for, and went ahead and broke it out into a standalone module because why not.

It’s super simple (just a couple dozen lines, mostly identical to what’s discussed upthread) but it’s one of those things that’s just too big to just cut and paste whenever it’s needed.

Usage is simple; there’s a gInterruptImplicit macro that will call exit after the current action if and only if the current action is implicit. The return value is true if the current action is implicit and nil otherwise. Example:

             if(gInterruptImplicit == true) {
                     "Implicit action interrupted. ";
             }

Repo is here: interruptImplicit github repo.

2 Likes

What kind of implicit actions are you looking to interrupt?

I’m not worried about interrupting specific actions, it’s more that I’m trying to prevent implicit actions in general in specific situations.

Say the player is a gentleman thief and is trying to steal the Crown Jewels of Frobnozzica. The museum they’re in has patrolling guards, and the jewels are behind a series of alarms, tripwires, and traps. The player is trying to disable the traps and get to the jewels while the guards are patrolling other parts of the museum. What I want to do in this situation is to interrupt any implicit action, to maintain a strict ordering of “player makes one move, NPCs make one move” every turn.

For example, consider the following situation:

  • Alice is a guard and she’s one turn away from entering the room with the jewels
  • The player has disarmed all the traps and alarms, and now the jewels are just sitting in the closed display case

In the normal course of things the player would just be able to use >TAKE JEWELS, and get:

>TAKE JEWELS
(first opening the display case)
Taken.

>

On the other hand without implicit actions, you’d get:

>OPEN DISPLAY CASE
Opened.

Alice enters from the north and starts looking around the room.

>TAKE JEWELS
Alice notices your brazen attempt at larceny and escorts you from the museum.

…or whatever. That is, you can use implicit actions to basically stack multiple actions into a single game turn. Most of the time that’s fine, because the timing of individual turns isn’t that important. But for this kind of situation I want to avoid it.

So instead I’m doing something like:

>TAKE JEWELS
(first opening the display case)
After carefully easing the lid of the display case open just enough for you to reach in, you stop what you're doing and try to act nonchalant.

Alice enters from the north and starts looking around the room.

>

…and so on.

I’m not actually writing a heist game, but I think this illustrates the kind of gameplay mechanic I’m worried about.

2 Likes

I gotcha. That makes sense.

1 Like