Checking action preconditions before noun disambiguation?

Does T3/adv3 provide any mechanism for causing an action to fail before any disambiguation prompts are given?

For example, say we want to be able to define a flag on a room to prevent the player from being able to >TAKE any objects while in the room. We can do something like:

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

class DemoRoom: Room
        takeFlag = nil
;

checkTakeFlag: PreCondition
        checkPreCondition(obj, allowImplicit) {
                local rm;

                if(((rm = gActor.getOutermostRoom()) == nil)
                        || (rm.takeFlag != true)) {
                        reportFailure('{You/he} can\'t take anything here. ');
                        exit;
                }

                return(nil);
        }
;

modify TakeAction
        preCond = [ checkTakeFlag ]
;

startRoom: DemoRoom 'Void'
        "This is a featureless void.  The other room is to the north. "
        north = otherRoom
;
+me: Person;
+blackStone: Thing '(black) (go) stone' 'black go stone' "A black go stone. ";
+whiteStone: Thing '(white) (go) stone' 'white go stone' "A white go stone. ";

otherRoom: DemoRoom 'The Other Room'
        "This is the other room.  The void is to the south. "
        south = startRoom
        takeFlag = true;
;
+rock: Thing 'ordinary rock' 'rock' "An ordinary rock. ";

versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;

This works as expected:

Void
This is a featureless void.  The other room is to the north.

You see a black go stone and a white go stone here.

>take black go stone
You can't take anything here.

But:

>take stone
Which stone do you mean, the white go stone, or the black go stone?

>black
You can't take anything here.

In this case forcing the player to provide disambiguation when all of the options would produce the same failure message seems…unfortunate.

But I don’t know of any way of forcing a failure/command termination prior to the disambiguation prompt, short of messing around (again) with parser internals.

Am I missing something?

1 Like

I may have downloaded a patch for this; I’ll have to check later…

I think the patch I was thinking of was addressing something a little different. I wonder if you could accomplish this in a few lines with a modification of filterAmbiguousWithVerify, on either Action or specific Actions… I have modified that method before when doing some weird tricks involving treating one object as a collective that could have any number of individuals added to it.

1 Like

In this case checking your flags and perhaps making sure the list returns only one choice?

if your check don’t change game state, you can put it in the verify phase, whose AFAICT precedes the disambiguation determination phase. Else, you can place it in the check phase (As I understand, the disambiguation is at the beginning of the action phase)

Best regards from Italy,
dott. Piergiorgio.

Unfortunately no, the verify() and check() are called after disambiguation. On transitive verbs they have to be: they’re going to live in SomeObjectOrClass.dobjFor(SomeAction), so they can’t be called until noun resolution has happened. And the same’s true of Action.verifyAction() and Action.checkAction().

Noun resolution occurs pretty early, and is one of those things where it it’s easy enough to fiddle with how noun resolution works for individual actions (that is, to decide whether or not an object should be in scope for an action, and things like that)…but there really aren’t any hooks for modifying noun resolution in the abstract (like adding a method to actions that is checked before noun resolution), short of just rewriting large chunks of the parser.

Of course after spending way too long getting the noun-as-verb stuff working “just rewrite large chunks of the parser” is in my wheelhouse, so I guess I could just do that. But I’m always hoping for something already in the library that I’ve just overlooked.

It looks like hacking together a before-noun-resolution equivalent to action preconditions is pretty straightforward. With an asterisk next to “straightfoward” that leads to a footnote mentioning that I already have a modular(-ish) executeCommand() replacement.

If you look at adv3’s executeCommand() there’s a parseTokenLoop: label on a for(;;){} loop. Inside that, there’s a bunch of stuff in a big try/catch() block, and in the try there’s a point where it decides on a matching action via match = rankings[1].match. Many lines later, the actual action is pulled out of the match via action = match.resolveFirstAction(issuingActor, targetActor);.

What I’m trying is floating the action assignment higher, to just before the if (match.hasTargetActor()) conditional block. This doesn’t change any of the logic, it just does the resolveFirstAction() a little earlier than it’s normally needed.

Immediately after the recently-promoted action = match.resolveFirstAction() line, I can now do something like:

                if(action != nil) 
                        action.preVerifyAction();

…where elsewhere we do…

modify Action
        resPreCond = static []
        preVerifyAction() {
                resPreCond.forEach(function(o) {
                        (o).checkResolverPreCondition();
                });
        }
;

…and…

class ResolverPreCondition: object checkResolverPreCondition() {};

This in turn allows us to do something like:

checkTakeFlag: ResolverPreCondition
        checkResolverPreCondition() {
                local rm;

                if(((rm = gActor.getOutermostRoom()) == nil)
                        || (rm.takeFlag != true)) {
                        reportFailure('{You/he} can\'t take anything here. ');
                        exit;
                }

                return(nil);
        }
;

modify TakeAction
        resPreCond = [ checkTakeFlag ]
;

…in the previously-posted demo code, which will now do what we want.

If this holds up…once again I wish there was a standard set of actions in a command file that could be used for regression testing…then I think that means I want to re-factor the noun-as-verb module I wrote previously (keywordAction) to make its modular executeCommand() replacement a separate module, and then implement this “resolver pre-conditions” logic as a delta to it.

1 Like

I’m going to preface this by saying, that I know you may end up wanting something more sophisticated. But if you’re just trying to accomplish the example given in the OP, this simple tweak will work.

modify TakeAction
	filterAmbiguousWithVerify(lst, b,c,d,e,f,g) {
		if(gPlayerChar.getOutermostRoom.noTakeFlag) return [lst[1]]; 
		else return inherited(lst,b,c,d,e,f,g); } 
;

//elsewhere define the noTakeFlag, and either the custom precondition
//or perhaps a simple roomBeforeAction statement

If you are only dealing with a handful of verbs, and your main concern is to not trigger a disambig prompt before displaying your fail message, this seems like a simple solution.
The tweak simply returns a single-item list so there is no disambig to do.

1 Like

This is a very clever approach to the problem. But yeah, I think I’m probably going to go with something less clever. Basically when I see something like that I ask myself “will I remember how this works a month from now” and I’m pretty sure I won’t.

I’d probably feel differently if I hadn’t already spent so much time digging around in the parser internals to get 90% of the way to a more modular solution, though.

1 Like

Fair enough :slight_smile:

I put up a git repo for a module containing a slight variation of the code I posed earlier here: resolverPreCondition git repo.

It depends on modularExecuteCommand, the executeCommand() replacement I put together.

After importing the module, using it is straightforward:

  • Define one or more instances of the ResolverPreCondition class. The check(s) should go in the object’s checkResolverPreCondition() method
  • Add any ResolverPreCondition instances to any action’s resPreCond property, which is a list.

Simple example, where we define a “noTake” flag on Room and then check it in TakeAction:

// Add a flag to the Room definition
class NoTakeRoom: Room noTake = true;

// Message used in the precondition
modify playerActionMessages
        cantTakeHere = '{You/he} can\'t take anything here. '
;

// The precondition declaration itself
checkTakeFlag: ResolverPreCondition
        checkResolverPreCondition() {
                local rm;

                if(((rm = gActor.getOutermostRoom()) == nil)
                        || (rm.noTake == true)) {
                        reportFailure(&cantTakeHere);
                        exit;
                }

                return(nil);
        }
;

// Add the precondition to TakeAction
modify TakeAction resPreCond = static [ checkTakeFlag ];

Complete compilable example in the demo directory of the module source.