General approach to interrupting arbitrary player actions, for scene-ish purposes

Anyone here fiddled around with mechanisms for interrupting normal command processing, but only temporarily?

For example, consider a spy thriller or something like that, where the player is at some point drugged and left in an interrogation room. If you want to override more or less everything, prohibiting most actions and giving custom responses for specific, permitted actions…this is straightforward enough to implement as a bunch of conditional logic living in the room itself, its scenery components, and any objects that might show up in the “scene”.

On the other hand if you’ve got something similar, only an enemy agent (or whatever) might drug the player more or less anywhere, then doing the same thing gets substantially more complicated, unless you want to write a bunch of elaborate conditional stuff into literally everything in the game.

The way I’ve been edging toward this problem is to implement an abstract “scene” object, kinda like I7’s (and the contrib module for T3 from Eric Eve), only with the addition of action-based triggers and the ability to script stuff by letting a scene object subscribe to global beforeAction() and afterAction() notifications.

This works for a lot of things, but there are a whole lot of weird corner cases, like the fact that all travel actions will call beforeAction() twice, each time with a different Action instance.

Anyway, I was wondering if anyone else had tackled this kind of thing as an abstract case. Early on I ran into Eric Eve’s scene.t, which is more geared toward doing things kinda like I7’s scenes. After I’d written a bunch of code I ran into Emily Boegheim’s reaction.t which does something kinda like what I’m talking about, but is more for doing things like intercepting specific commands. But I don’t know of anything that’s designed for the sort of thing I’m talking about: basically changing everything, but only temporarily.

I don’t know a lot about it, but it wouldn’t surprise me if you could tackle this problem a lot easier if you were using Adv3Lite. The ‘doer’ class looks like it might be able to handle this.

1 Like

Yeah, I’m willing to believe that. Unfortunately I’m several thousand LOC past the point where I want to swap libraries.

I’ve used actorAction for suspending most verbs temporarily, but perhaps not on as grand of a scale as you’re going for. I guess it depends if you want custom dismiss messages for any number of rooms/objects, instead of just “You’re drugged… that’s not going to work very well.” The latter seems to be doable with actorAction (on the PC)…

Yeah, that’s one of the (several) approaches I tried. Main difficulty (in my case) with this is that I want/need NPCs to play by the same rules as the player, in most cases.

Basically I’ve got situations where the player and some NPCs will devise a plan, delegating tasks to each, and then they all head out in different directions to do whatever they’re supposed to do. So if there’s a “scene” that changes the rules if the player is doing it, I want the same thing to happen to whatever NPC happens to be doing whatever it is instead. Hopefully without having to code up a bunch of special cases (like what happens if the player was following the NPC and witnesses the scene as a spectator instead of a participant).

In theory I could just add whatever elaborate conditional stuff I need to Actor, or create a SceneActor class for the player and all eligible NPCs or something like that…but I’m somewhat leery of handling it this way, because I’ve got a lot of scenes and a fair number of NPCs.

So I’ve been trying to convert all the special cases and one-offs into something that can be put into more or independent “scene objects” that hold all the logic for the various special cases.

1 Like

Related to the general problem I’m asking about in this thread:

Say I want to use beforeAction() (or something similar) to override an action…but only if the action would otherwise succeed. Is there a more straightforward way to handle this than using a try(){} finally(){} block to attempt the action and check the result?

I’ve had situations where I wished I could know if the action would pass verify before deciding what to do with it, but I’ve never needed it urgently enough to work out a smooth solution

Something like this works, added to whatever object definition it needs to be in (in my case the abstract scene class):

        // Flag we'll use below to prevent a stack overflow
        _testCurrentActionLock = nil

        // Returns boolean true if the action would succeed, nil otherwise
        testCurrentAction() {
                local t;

                // Make sure we're not recursing.
                if(_testCurrentActionLock == true)
                        return(nil);
                _testCurrentActionLock = true;

                // Save the "real" transcript.
                t = gTranscript;

                try {
                        // Save the current game state.
                        savepoint();

                        // Create a new transcript and execute the
                        // current command.
                        gTranscript = new CommandTranscript();
                        executeCommand(gActor, gActor,
                                gAction.getOrigTokenList(), true);

                        // Return true if the command succeeded, nil
                        // otherwise.
                        return(!gTranscript.isFailure);
                }
                finally {
                        // Revert to the old game state.
                        undo();

                        // Clear our lock.
                        _testCurrentActionLock = nil;

                        // Restore the old transcript.
                        gTranscript = t;
                }
        }

Then you can do something like

        beforeAction() {
                //
                // Additional action checks go here, of the form:
                //
                //     if(check_for_allowed_action_type)
                //          return;
                //

                // Return if the action would fail anyway.
                if(!testCurrentAction())
                        return;

                // Display our custom "you can't do that" message.
                defaultReport('Bob moves to block you.  <q>We\'re closed,</q>
                        he says. ');
                exit;
        }

…and so on. That allows you to defer to the default failure messages if applicable: so if you’ve got Bob blocking the player from using any exits except the one to the south, then if the player tries to go east and there’s no exit in that direction you’d get:

You can't go that way.

instead of

Bob moves to block you.  "We're closed," he says.

…or whatever.

Like I said, that works, but it’s pretty heavyweight: you’re basically doubling the amount of work the parser is doing to handle the command. Not a big deal if used sparingly, but it’s something where you definitely need to tack the expensive part at the tag end of your checks so it gets called as infrequently as possible.

It would be nice if there was some neater way to insert checks/subscribe to notifications at arbitrary points in the main parser loop. Like in this example it would be nice if we could just insert ourselves into the very end of processing, after everything else has passed the action but before the game state actually changes.

1 Like

I have actually done something akin to this very thing; the trick lies in appropriate handling of roomBeforeAction, as in the snippet below from my actual coding:

roomBeforeAction()
  {
        if (!painting.described && !gAction.ofKind(SystemAction)
&& !gActionIn(Look, Inventory, Examine)) {
                failCheck ('Good ol\' wisdom suggest you that is better examining 
        yourself and your immediate environment prior of bravely threading this 
        new environment..\n<i>this very realistic painting is very 
interesting...</i>');
        }
        
        if (!painting.described && gActionIs(Look))


similiar handling for the other allowed verbs, in the case above, after LOOK, there are the handling of INVENTORY and EXAMINE.

HTH and

Best regards from Italy,
dott. Piergiorgio.

1 Like

That’s one of the approaches I started out with—some bits in Room declarations, some in Actor declarations, and so on. I think that works well enough for “static” scene-like things (that is, scenes that are guaranteed to occur in a specific room), I feel like it starts to get out of hand for “floating” scenes (scenes that can happen more or less anywhere).

I also I’m leaning toward implementing an abstract class to hold all the scene-related nuts and bolts just for modularity/general code cleanliness. But that’s mostly just personal aesthetic preferences.

Anyway, back when I had a bunch of the code living in the room definitions, I experimented with making room “states”, a la ActorState. This is something that I think is kinda underutilized in T3/adv3—I’d like a generic Thing/ThingState system by which you could delegate any behavior to a state object, if defined (instead of just a couple of specific ones, as the existing ThingState and ActorState do).

I didn’t really flesh the idea out, but my toy StatefulRoom implementation was something like:

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

#include "reflect.t"

class StatefulRoom: Room
        statefulRoomState = nil

        roomBeforeAction() {
                if(statefulRoomState == nil)
                        return;
                statefulRoomState.beforeAction();
        }
;

class StatefulRoomState: object
        _senseActions = static [ ExamineAction, LookAction, SmellAction,
                ListenToAction, SenseImplicitAction ]
        _travelActions = static [ TravelAction, TravelViaAction ]

        _canonicalizeAction(action?) {
                if(action == nil)
                        action = gAction;

                if((action == nil) || !action.ofKind(Action))
                        return(nil);

                return(action);
        }
        _checkList(action, lst) {
                if((action == nil) || (lst == nil))
                        return(nil);
                return(lst.valWhich({x: action.ofKind(x)}) != nil);
        }
        isSenseAction(action?) {
                return(_checkList(_canonicalizeAction(action), _senseActions));
        }
        isTravelAction(action?) {
                return(_checkList(_canonicalizeAction(action), _travelActions));
        }
        beforeAction() {}
;

startRoomDefault: StatefulRoomState
        beforeAction() {
                if(isSenseAction())
                        return;

                if(gAction.ofKind(EnTravelVia))
                        return;
                if(isTravelAction() && (gAction.getDirection == northDirection))
                        return;

                defaultReport('A mysterious force prevents your actions. ');
#ifdef DEBUG_ACTION
                reportAfter('Action was
                        <<reflectionServices.valToSymbol(gAction)>>. ');
#endif // DEBUG
                exit;
        }
;

startRoom: StatefulRoom 'Void'
        "This is a featureless void.  Additional rooms lie to the north
                and south. "

        north = otherRoom
        south = forbiddenRoom

        statefulRoomState = startRoomDefault
;
+me: Person;
+pebble: Thing 'small round pebble' 'pebble'
        "A small, round pebble. "
;
otherRoom: Room 'Other Room'
        "This is the other room.  The void is to the south. "
        south = startRoom
;
forbiddenRoom: Room 'Forbidden Room'
        "This room is forbidden in a pointlessly complicated way. "
        north = 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 main “gimmick” or whatever here being that you can twiddle states, so you can stuff comparatively expensive logic into multiple scenes without having to worry about having to carefully order everything to prevent performance bottlenecks.

2 Likes

It so happens I am also deeply down a ‘generic state’ implementation, though mine is pretty bare bones. It turns out you can coopt ActorStates and assign them to non-Actors. Sadly, they don’t automatically schedule TakeTurns, so you have to stick to activate/deactivate methods to do any work.

class orchestratorState : HermitActorState
    location = containerObjInst // or assign through '+' ownership as desired
    isInitState = nil // set to one Instance in your orchestrator
    activateState(actor, oldState) {
        // do most per-instance work here
    }
    deactivateState(actor, newState) {
        //  or here
    }
;
containerObjInst : [anything you want really]
     curState = nil // will get set by the state inst that sets isInitState = true
     setCurState(state) { return delegated Actor(state); }
;

So far this has been enough for me. I suspect if I really dig in, I could create my own orchestratorState without needing HermitActor, but haven’t gone down that road yet.

1 Like