Handling verify() in dobjFor(Default)

I would like, or I think I would like, to use a verify() method in dobjFor(Default) for some objects. Specifically the situation I’m looking at is card-playing “stuff”, to cause actions in general to fail with a specific illogicalNow, with the exception of a few specifically allowed actions.

But doing anything with verify() on dobjFor(Default) borks all the other action verification on the object. For example, given:

pebble: Thing 'small round pebble' 'pebble'
        "A small, round pebble. "

        dobjFor(Default) { verify() { inherited(); } }
;

This gets you:

Void
This is a featureless void with a table.

You see a pebble here.

>sit
(in the pebble)

>talk to pebble
Nothing obvious happens.

Note that the >SIT output wasn’t messed up by me cutting and pasting it, the action picks pebble as the implicit object and then produces no output.

I assume this is just a consequence of how dobjFor(Default) is handled…although the same thing doesn’t appear to be true for a check() method on dobjFor(Default) (but I don’t know if that’s just because there seem to be fewer builtin check()s compared to verify()s in stock adv3).

Is there an elegant way to handle this? Or am I just approaching this in a fundamentally silly way (i.e. I shouldn’t be approaching it with dobjFor(Default) at all)?

1 Like

As I understand dobjFor(Default) it is designed to be used to provide a single specific response to all verbs where not otherwise specified by that object. I would speculate that if you are intending it to inherit behavior of other actions, it is not set up to do that. Rather, it will replace ALL actions verify() with your specified one. If that is what you mean by ‘borking’ then yes, I think it is explicitly designed to do that!

What I am unclear on is what the parser thinks you are inheriting verify() FROM, as Thing has no definition for dobjFor(Default). Based on your example, it appears that verify() { inherited(); } is allowing the SitOn action, but because you don’t define an action() here (or inherit one from Thing) it shows nothing. In general, this definition might actually allow ALL verbs!

I seem to be using it in my WIP for the purpose you are describing, and doing so successfully as such:

+ npcPrivateStash : Thing 'npc\'s private stash' 'private stash'
    "The NPC\'s private stash.  "
    owner = NPC
    isListed = nil
    dobjFor(Examine) { verify() { } }
    dobjFor(Default) { verify() { illogical('The NPC is unlikely to allow that.   '); } }
;

In my case, any action except EXAMINE results in failing the verify with the above message, not other verb default messaging. EXAMINE only works because of the line above it, which ‘grandfathers’ it out of the Default. If I comment that out, even EXAMINE gets the ‘NPC is unlikely…’ message.

Right. And you can get the behavior I’m looking for by, for example, going through each other action handler and just doing something like:

dobjFor(Read) { verify() { inherited(); } }
dobjFor(LookIn) { verify() { inherited(); } }

…and so on. That is, not adding any new behavior, just putting the dobjFor() for each action directly on the object, which means it will then have precedence over dobjFor(Default).

I’m just looking for a clean way of doing that.

Sorry, I think I’m missing something fundamental then. What do you need dobjFor(Default) for? Doesn’t Thing already provide a wealth of default per-Verb ‘can’t do that’ messaging for other than examines or takes?

I want something like:

pebble: Thing 'small round pebble' 'pebble'
        "A small, round pebble. "

        hotFlag = true

        dobjFor(Default) {
                verify() {
                        if(hotFlag == true)
                                illogicalNow('{You/He} can\'t do that, it\'s
                                        too hot right now. ');
                        inherited();
                }
        }
;

In other words to check for a specific condition and have everything fall through to be handled normally if the condition doesn’t apply.

I already have a very heavyweight way of handling this via the scene module I wrote, but I’d like a way of doing this without requiring it.

I see. I haven’t tried it yet, but I think what you might want is dobjFor(All) The manual seems to suggest it DOES fall through to default behavior if not prevented. Untested code:

dobjFor(All) {
    verify() { if (hotFlag == true) illogicalNow('Too hot in the hot tub!  '); }
}

I am unclear though if it operates on the same ‘exceptions must be specified in object’ paradigm.

EDIT: quick trip through the code reinforces this may be what you want

You can’t use other action handlers with dobjFor(All).

I don’t think I get your meaning there. Can you give an example of what action handlers are disallowed? Here is a sample game that seems to work per what I understand your goals to be?

startRoom : OutdoorRoom 'Front Yard'
    "This is a featureless front yard.  A house is to the north. "
    north = house
    in asExit(north)
    vocabWords = 'outside'
;
+me : Person;
+rock : Thing 'ordinary rock*rocks stones pebbles' 'rock'
    "An ordinary rock.  "
;
+stone : Thing 'notable stone*rocks stones pebbles' 'stone'
    "A notable stone.  "
;

house : Room 'Inside House'
    "This is the one room house (apparently with no door).
        The front yard lies to the south.  "
    vocabWords = 'inside/house'
    south = startRoom
    out asExit(south)
;
+chair : Chair 'simple chair' 'simple chair'
    "A simple chair.  "
    dobjFor(All) {
        verify() { if (me.contents.length())
            illogicalNow('Too many stones on you.  '); }
    }
    dobjFor(SitOn) {
        action() {
            inherited;
            "About time!  ";
        }
    }
;
+pebble : Thing 'extraordinary pebble*rocks stones pebbles' 'pebble'
    "An extraordinary pebble.  "
;
gameMain : GameMainDef
    initialPlayerChar = me
;

Below, everything is forbidden when I hold rocks, as is standard with chairs. Then, when I drop, default handling is accomodated, including newly defined one.

Front Yard
This is a featureless front yard.  A house is to the north.

You see a stone and a rock here.

>get rock
Taken.

>in
Inside House
This is the one room house (apparently with no door).  The front yard lies to
the south.

You see a simple chair and a pebble here.

>sit on chair
Too many stones on you.

>move chair
Too many stones on you.

>kick chair
Too many stones on you.

>drop rock
Dropped.

>kick chair
You cannot attack that.

>sit on chair
About time!

>

Oh wait, if what you mean is you cannot have other unique verify()'s yes, that’s mostly right. If those others are few in number though, you could do

dobjFor(All) {
    verify() {
         if ((hotFlag == true)
             && !gActionIn([actions you don't want handled by ALL]))
              illogicalNow('Too hot in the hot tub!  ');
    }
}

The presumption being that list is manageable.

The problem is that dobjFor(All) does what it says, which is preempt all other action handlers. Which is not what I want. What I want is to preempt any action without an explicit action handler (which is what dobjFor(Default) does)…but only if some condition applies. And if the condition doesn’t apply, I want it to fall through to whatever behavior would have happened if the check wasn’t there in the first place.

As a concrete example: like I said, I was looking at card-playing items in and out of a game. Here let’s just consider a deck of cards. Outside of a game, it should just be more or less a normal Thing: you can pick it up, put it in containers, try to light it on fire, whatever. In a game, it should block all actions except those specifically permitted as part of gameplay. Some code:

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

startRoom: Room 'Void' "This is a featureless void. ";
+me: Person;
+deck: Thing 'deck (of) cards' 'deck of cards'
        "It's a deck of playing cards. "

        gameFlag = true

        // THIS DOESN'T WORK, DON'T COPY IT
        dobjFor(Default) {
                verify() {
                        if(gameFlag == true)
                                illogicalNow('{You/He} can\'t mess with
                                        the deck during a game. ');
                        inherited();
                }
        }
        dobjFor(Examine) { verify() {} }
        dobjFor(Shuffle) {
                verify() {
                        if(gameFlag == true)
                                illogicalNow('They\'ve already been shuffled. ');
                }
                action() {
                        defaultReport('Okay, the cards are now shuffled. ');
                }
        }
        dobjFor(Discard) {
                verify() {
                        if(gameFlag == nil)
                                illogicalNow('{You/He} can only discard during
                                        a game. ');
                }
                action() {
                        defaultReport('Okay, the cards have been discarded. ');
                }
        }
;

versionInfo: GameID;
gameMain: GameMainDef initialPlayerChar = me;

DefineTAction(Shuffle);
VerbRule(Shuffle)
        'shuffle' singleDobj
        : ShuffleAction
        verbPhrase = 'shuffle/shuffling (what)'
;
modify Thing
        dobjFor(Shuffle) {
                verify() { illogical('{You/He} can\'t shuffle that. '); }
        }
;

DefineTAction(Discard);
VerbRule(Discard)
        'discard' singleDobj
        : DiscardAction
        verbPhrase = 'discard/discarding (what)'
;
modify Thing
        dobjFor(Discard) {
                verify() { illogical('{You/He} can\'t discard that. '); }
        }
;

We get two new actions, >SHUFFLE [something] and >DISCARD [something] (neither of which actually do anything except output informational messages in this example).

As written, the above code does what I want when there is a game in progress (that is, when the gameFlag on the deck is true. Then most actions will fail with a message telling the player they can’t mess with the deck during a game, with the exception of >EXAMINE and the card-specific actions.

But then if gameFlag is not true:

Void
This is a featureless void.

You see a deck of cards here.

>sit
(in the deck of cards)

…the normal behavior of the object is borked.

If instead of using dobjFor(Default) we use dobjFor(All), then everything works find when we’re not in a game, but when we are in a game the things we want to be exceptions still fail.

I guess one solution is to go down the road of basically building the entire action-handling logic for the object in a catchall dobjFor(All), but that’s going to rapidly get out of hand for the kind of things I want to do (in this example the conditional being checked has a single clause—is the game flag set—but in the real code different actions are valid only during specific phases of play) and it’ud make it very challenging to handle things in abstract classes.

No checking on my part as yet… I’ve used dobjFor(Default) a lot and don’t rememeber any serious issues, so I’d have to scout around to see what I did.
But does this work?
EDIT: it doesn’t work

dobjFor(Default) {
                verify() {
                        if(gameFlag == true)
                                illogicalNow('{You/He} can\'t mess with
                                        the deck during a game. ');
                        inherited();
                }
                check { inherited; }
                action { inherited; }
                preCond { return inherited; }
                remap { return inherited; }
        }

This doesn’t address the conditional thing, but this is the method I used to easily exclude verbs from being trapped by Default:

#define INH { preCond { return inherited; } \
	remap { return inherited; } \
	verify() { inherited(); } \
	check() { inherited(); } \
	action() { inherited(); } }
#define mapINH(vb...) vb#foreach: dobjFor(vb) INH : :
 // there's also an Iobj version

+baby: Person '' '' ""  
  dobjFor(Default) { verify { illogical('For the most part, the baby 
       isn\'t your business, and you\'re going to leave her alone. '); } }
 // everything in this list will always get normal handling
mapINH(Examine,Watch,ListenTo,AskAbout,AskFor,TellAbout,PlayWith,Rub,Pet)
...
;

You could also take into consideration:

modify Action
  callCatchAllProp(obj, actionProp, defProp, allProp) { 
       return obj.gameFlag || obj.alwaysUsesCatchalls; }
   or  return obj.hasCatchallConds ? obj.catchallConds : inherited(a,b,c,d); }

or something to that effect…

1 Like

Right. I see now. I am not aware of a way to bend Default or All to your needs here. If qualifying All with if (phase && !gActionIn(...)) clauses is too convoluted (which would probably be my approach), the only other option might be to invent an AllDefault.

To summarize I think the problem:

  • dobjFor(Default) exempts for existing handler definitions but does not allow handler ‘fall through’
  • dobjFor(All) allows handler ‘fall through’ but does not exempt for existing definitions
  • you want something that does both

Thought I had a chance to help on this one, sorry.

1 Like

Yeah. And I really don’t want to use an approach where I end up having to touch multiple bits of code whenever an action handler is added to an object (so adding a dobjFor(SomeAction) and then also having to remember to add SomeAction to the dobjFor(All) stanza.

Unfortunately I don’t know of any way of just iterating over all the handlers declared on an object. You can kinda fake it if you’ve compiled with reflectionServices, via something like:

        _actionTable = nil
        _regex = nil

        v2s(v) { return(reflectionServices.valToSymbol(v)); }

        initActionTable() {
                local obj;

                if(_actionTable != nil)
                        return;

                _actionTable = new LookupTable();

                for(obj = firstObj(Action, ObjClasses); obj != nil;
                        obj = nextObj(obj, Action, ObjClasses))
                        _actionTable[v2s(obj)] = obj;
        }

        checkActionHandlers() {
                local fn, i, l, m, r;

                initActionTable();

                l = getPropList();
                r = new Vector();
                if(_regex == nil)
                        _regex = new RexPattern('verifyDobj(<AlphaNum>+)');
                l.forEach(function(o) {
                        if(!propDefined(o, PropDefDirectly))
                                return;

                        if((fn = v2s(o)) == nil)
                                return;

                        m = fn.findAll(_regex, function(match, index, act) {
                                if(act == nil)
                                        return(nil);
                                return(act);
                        });

                        if(m == nil)
                                return;

                        m.forEach(function(o) {
                                o = '<<o>>Action';
                                if(_actionTable[o] == nil)
                                        return;
                                r.append(_actionTable[o]);
                        });
                });

                for(i = 1; i <= r.length; i++) {
                        if(gAction.actionOfKind(r[i]))
                                return(true);
                }

                return(nil);
        }

…and then calling checkActionHandlers() in verify() in dobjFor(All). But that’s really ugly.

I might look at trying to redefine dobjFor() and/or objFor() (which are preprocessor macros) to build an index of action handlers on the object, but I’m not sure if that’s possible with the preprocessor semantics.

I verified that this works, insofar as I understand your intent:

modify Thing
	hasCatchallConds = nil
	catchallConds = nil
;
modify Action
	callCatchAllProp(obj, actionProp, defProp, allProp) { 
		if(obj.hasCatchallConds && !obj.catchallConds) { return nil;}
		return inherited(obj, actionProp, defProp, allProp);
		}
;

+deck: Thing '' '' ""
   dobjFor(Default) { verify{ illogical('Game underway'); } }
   hasCatchallConds = true
   catchallConds = gameFlag // could be a complex method also
   gameFlag = nil
   dobjFor(Turn) {verify{}check{}action{ "Flipped."; gameFlag = !gameFlag; }}
   dobjFor(Feel) {verify{} action{ if(gameFlag) "GameFlag on."; else "GameFlag off.";}}
;
;

Default won’t be called at all unless gameFlag is true. All actions give normal responses when gameFlag is false. If gameFlag is true, Feel and Turn will overpower the Default and proceed with their own handling; all others will respond with “game underway”.

1 Like

hmmmmmm… mumbleee…

perhaps is simpler, if not even wiser, having TWO deck, one outside gaming, and the other when playing, swapping the decks at the start and end of the gaming table session ? crude, oldskool coding technique, but looks to me much simpler, and perhaps also effective…

Best regards from Italy,
dott. Piergiorgio.

Yeah, I don’t hate using Action.callCatchAllProp() to handle this. I’d looked at modifying Action.callVerifyProp() (which is where the callCatchAllProp() use we care about is) but it’s a lot messier than callCatchAllProp() is.

I think the thing that accomplishes what I want that’s most “harmonious” with the design of the rest of adv3 is something like:

modify Action
        // Boolean flag.  If true, individual verify properties will
        // be called even if the default property has precedence.
        conditionalDefault = nil

        // Set the conditional default flag
        setConditionalDefault() { conditionalDefault = true; }

        // This is logically equivalent to the stock adv3 method, with
        // the one addition of the check for the conditionalDefault flag.
        callCatchAllProp(obj, actionProp, defProp, allProp) {
                obj.(allProp)();
                if(!obj.propHidesProp(actionProp, defProp)) {
                        obj.(defProp)();

                        // If the conditionalDefault flag is set, we return
                        // nil even though we called the default property.
                        // This is the only logical difference from the
                        // stock method.
                        if(conditionalDefault)
                                return(nil);
                        else
                                return(true);
                } else {
                        return(nil);
                }
        }
;

…and then adding a #define:

#define ignoreDefault (gAction.setConditionalDefault())

…which then lets you do something like…

        dobjFor(Default) {
                verify() {
                        if(gameFlag == true)
                                illogicalNow(&cantFiddleWithCards);
                        else
                                ignoreDefault;
                }
        }

This will behave like a stock adv3 dobjFor(Default) stanza if gameFlag is true. That is:

  • if a ``dobjFor(All)` is defined it will still be called (and called first)
  • any dobjFor([action]) stanzas for the action will be called if they are defined directly on the object, and the Default stanza will be preferred to inherited stanzas

And if gameFlag is nil this will behave as if the dobjFor(Default) stanza wasn’t defined at all.

Went ahead a put this into another tiny (~100 LOC) module: conditionalDefault github repo.

2 Likes

That’s more or less what I started out with, a couple of refactors ago. I ended up abandoning that approach after I started using the deck object as a container for handling a bunch of the intangible card-related stuff (vocabulary, Unthings, and so on).

1 Like