Library messages for verbs with unusual scope rules

Say we have a verb whose scope is not limited by physical presence, call it >TWIDDLE, and two classes of object: those that are twiddlable and those that are not twiddlable.

Here’s a simple game-thing that implements >TWIDDLE [object] and a twiddlable pebble and a non-twiddlable rock.

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

DefineTAction(Twiddle)
        // All memorable objects are in scope, and all actors
        objInScope(obj) {
                return(obj.ofKind(Twiddlable));
        }
;
VerbRule(Twiddle)
        'twiddle' singleDobj : TwiddleAction
        verbPhrase = 'twiddle/twiddling (what)'
;
modify Thing
        dobjFor(Twiddle) {
                verify() { illogical('You can\'t twiddle that. '); }
        }
;

class Twiddlable: Thing
        dobjFor(Twiddle) {
                verify() {}
                action() {
                        defaultReport('{You/he} twiddle{s} {it/him dobj}. ');
                }
        }
;

startRoom:      Room 'Void'
        "This is a featureless void."
        north = otherRoom
;
+me:    Person;
+pebble: Twiddlable 'small round pebble' 'pebble'
        "A twiddlable small round pebble. "
;
otherRoom:     Room 'Other Room'
        "This is a different room. "
        south = startRoom
;
+rock: Thing 'ordinary rock' 'rock'
        "An ordinary, untwiddlable rock. "
;

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 “interesting” bit is that the Twiddle action declaration does:

DefineTAction(Twiddle)
        // All memorable objects are in scope, and all actors
        objInScope(obj) {
                return(obj.ofKind(Twiddlable));
        }
;

…to ensure all twidlable objects are always in scope for the action. But this has the side effect of causing a weird behavior for any non-twiddlable object:

>twiddle rock
You see no rock here.

…regardless of whether or not there’s a rock in the room.

This feels like it’s probably a simple thing that I’m just missing because I’m conceptualizing the whole thing wrong, but I can’t figure out what it is. Probably because I’m conceptualizing the whole thing wrong.

1 Like

Hm. Maybe something like putting everything in scope and then doing the sorting in the verify() block of Thing.dobjFor(Twiddle), via liberal use of dangerous to silently bump things out of the list of resolved objects while leaving them in scope?

1 Like

So, here’s something sorta like what I describe in the second post:

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

DefineTAction(Remember)
        objInScope(obj) { return(true); }
;
VerbRule(Remember)
        'remember' singleDobj : RememberAction
        verbPhrase = 'remember/remembering (what)'
;
modify Thing
        dobjFor(Remember) {
                verify() {
                        if(gActor.canSee(gDobj))
                                illogicalNow('You don\'t have to remember it,
                                        you can see it now. ');
                        if(!gActor.hasSeen(self))
                                dangerous;
                }
                action() {
                        defaultReport('You can\'t remember that. ');
                }
        }
;

class Memorable: Thing
        dobjFor(Remember) {
                action() {
                        defaultReport('{You/he} remember{s} {it/him dobj}. ');
                }
        }
;

startRoom:      Room 'Void'
        "This is a featureless void."
        north = otherRoom
;
+me:    Person;
+pebble: Memorable 'small round pebble' 'pebble'
        "A memorable  small round pebble. "
;
+blueRock: Thing 'blue rock' 'blue rock'
        "An unmemorable blue rock. "
;
otherRoom:     Room 'Other Room'
        "This is a different room. "
        south = startRoom
;
+redRock: Thing 'red rock' 'red rock'
        "An unmemorable red rock. "
;

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
;

Instead of >TWIDDLE [thing] we now have >REMEMBER [thing]. Objects are either memorable or unmemorable, but independent of that we worry about whether or not the player has seen objects. We put literally every object in scope for REMEMBER, but then bump objects we’ve never seen out of the resolved objects list by declaring them dangerous in the verify() block of Thing.dobjFor(Remember). This applies to all objects, regardless of whether or not they’re Memorable. We also, and this doesn’t really have anything to do with the general class of problem I’m asking about in the thread, check to see if the object is currently visible, but that’s just for consistency with a “remember” verb.

This is closer to the kind of thing I want to do, but it still feels like I’m handling things as special cases instead of being able to just stuff the “decisionmaking” into a single method or whatever.

And to be clear the thing I’m worried about is:

  • Having verbs consider objects as in scope independent of normal sense containment rules
  • Nevertheless keep unknown/never-seen/whatever objects out of scope, so disambiguation messages aren’t a form of reconnaisance
  • Have sensible failure reports for out of scope objects that don’t (incorrectly) imply that they’re out of scope just because they’re not immediately present/in the player’s current sense table—if you can TWIDDLE a pebble regardless of where it is, I don’t want the game suggesting that you can’t twiddle a rock because you can’t see it.

For a more concrete sort of example, imagine a YELL AT [something] or YELL FOR [something] verb that works in regions that don’t directly map to normal TADS3 sense containment rules. E.g., in one case you might want things in scope to be anything inside the same building as the player when they yell. In another it might be everything in an outdoor room location within some number of steps. That kind of thing.

1 Like

I haven’t tested this, but I suspect that this is because your definition of objInScope() replaces the library’s, so that even if the rock is in the room you’ve now defined it as not being in scope, so the adv3 parser takes it as being absent (and hence not something the player character can see).

Maybe the fix here is to use the inherited handling alongside your own scope definition:

DefineTAction(Twiddle)
       
        objInScope(obj) {
                return inherited(obj) || obj.ofKind(Twiddlable);
        }
;

This should place all Twiddable objects in scope alongside all objects that are physically present, so that you should now get the “You can’t twiddle that” message when you try to twiddle the rock.

1 Like

Yeah, it’s 100% this. I was looking an alternative way to basically either a) customize the scope rules (so as to handle everything in one method or something like that, instead of ending up with the custom scope logic smeared out across the action and the action handlers on Thing and so on), or b) to customize all of the out-of-scope failure messages and so on for specific actions.

Your modification to TwiddleAction.objInScope() fixes part of this (you’ll no longer be told that you can’t see the rock if you’re in the same room as the rock), but you will be told that you can’t twiddle the rock if you’re not in the same room as it, despite twiddlability not being location based. If that makes sense.

It’s probably more confusing than it needs to be because my first example is nonsensical and so there’s no intuition about what “twiddlability” implies. But it wouldn’t make sense to tell a player that they can’t remember something because it’s not in the room, or that you can’t yell for help to a character that’s downstairs. That kind of thing.

In more abstract terms, I’ve got a bunch of stuff that I want to be able to do in a game that involves coordinating actions between the player and NPCs, and so I’m going to have a lot of situations where I want to twiddle the scope of actions. Or at least I think that’s what I want. But I haven’t figured out any cleaner/more modular way of accomplishing this sort of thing than the way it’s done in the second (“remember”) example…where the “trick” is to put everything in scope and then do a bunch of kludgy nonsense with verify().

Hm. Is there a (straightforward) way to implement new “senses” in TAD3/adv3?

1 Like

Another approach, using preconditions. The problem (in terms of code cleanliness) is that as near as I can tell there’s no way to handle this entirely in the PreCondition—you still end up having to play with dangerous in verify to prevent disambiguation prompts for unknown objects.

The code:

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

canTwiddle: PreCondition
        checkPreCondition(obj, allowImplicit) {
                if(obj == nil)
                        obj = gActor;

                if(gDobj == nil) {
                        reportFailure('No object, bailing. ');
                        exit;
                }
                if(!gActor.hasSeen(gDobj)) {
                        reportFailure('Don\'t know about it. ');
                        exit;
                }
                if(!gDobj.ofKind(Twiddlable)) {
                        reportFailure('Not twiddlable. ');
                        exit;
                }
        }
;
DefineTAction(Twiddle)
        preCond = [ canTwiddle ]
        objInScope(obj) { return(true); }
;
VerbRule(Twiddle)
        'twiddle' singleDobj : TwiddleAction
        verbPhrase = 'twiddle/twiddling (what)'
;
modify Thing
        dobjFor(Twiddle) { verify() { if(!gActor.hasSeen(self)) dangerous; } }
;

class Twiddlable: Thing
        dobjFor(Twiddle) {
                action() {
                        defaultReport('{You/he} twiddle{s} {it/him dobj}. ');
                }
        }
;

startRoom:      Room 'Void'
        "This is a featureless void."
        north = otherRoom
        south = southRoom
;
+me:    Person;
+pebble: Twiddlable 'small round pebble' 'pebble'
        "A twiddlable small round pebble. "
;
+redRock: Thing 'red rock' 'red rock'
        "An untwiddlable red rock. "
;
otherRoom:     Room 'Other Room'
        "This is a different room. "
        south = startRoom
;
+blueRock: Thing 'blue rock' 'blue rock'
        "An untwiddlable blue rock. "
;
southRoom:      Room 'South Room'
        "This room has no rocks. "
        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
;

Transcript:

Void
This is a featureless void.

You see a pebble and a red rock here.

>twiddle rock
Not twiddlable.

>s
South Room
This room has no rocks.

>twiddle rock
Not twiddlable.

>n
Void
This is a featureless void.

You see a pebble and a red rock here.

>n
Other Room
This is a different room.

You see a blue rock here.

>s
Void
This is a featureless void.

You see a pebble and a red rock here.

>s
South Room
This room has no rocks.

>twiddle rock
Which rock do you mean, the blue rock, or the red rock?

>blue
Not twiddlable.

This works, more as far as it goes. My concern is that it feels kludgy and the underlying logic (and what would happen if you fiddle with any of the bits) isn’t particularly apparent. So it’s probably a trap for the unwary.

Sideways related to all of this, what are the options for handling multiple game objects that represent single “memetic” objects when you have scope-crossing like this? So scenery items and things like that—if you see the white house from the field and from behind the house and from inside the house, you might have multiple “white house” objects that give different descriptions representing the different viewpoints. But if you implement a scope-spanning action like “remember”, you (probably) don’t want to get disambiguation prompts over this sort of thing.

One solution is to use a single floating scenery object via something like MultiLoc, although then you probably end up with a bunch of conditional spaghetti to handle all the different vantage points. You can also use something like NameAsOther, although that gets messy if you need to twiddle things by vantage point (adjectives that work for one view of the object and not from others).

Just wondering if there’s a simple solution that I’m missing.

1 Like