Testing for a class of verbs (TADS3)

The specific problem: I have a character who is asleep, and I want any verb involving touching him to be intercepted and handled with a “Leave him alone” message.

I did this with beforeAction, which works with verbs that I have created, but something is catching verbs that are already in the library and giving the default msg.

So here’s what I have done so far:

In my header file:

#define DefineTouchTAction(name) \ 
    DefineTouchTActionSub(name, TouchTAction) 


#define DefineTouchTActionSub(name, cls) \ 
    DefineAction(name, cls) \ 
    verDobjProp = &verifyDobj##name \ 
    remapDobjProp = &remapDobj##name \ 
    preCondDobjProp = &preCondDobj##name \ 
    checkDobjProp = &checkDobj##name \ 
    actionDobjProp  = &actionDobj##name \ 

In my verbs file:


class TouchTAction: TAction 
; 

DefineTAction(Untape); 

VerbRule(Untape) 
    ('untape') 
        singleDobj 
    : TouchTAction, UntapeAction 
    verbPhrase = 'untape/untaping (what)' 
    askDobjResponseProd = singleNoun 
; 

modify Thing 
     dobjFor(Untape) 
     { 
        preCond = [touchObj] 
        action () 
        { 
            "You don't see any tape on {the dobj/him}. "; 
        } 
    } 
; 

In my character file:


ch_jack... 
beforeAction { 
        if(gAction.ofKind(TouchTAction)) 
        { 
            if (ch_jack.curState == jackSleeping) { 
                "Aww, he looks so cute while he's asleep. Why don't you leave the poor guy alone. "; 
                exit; 
            } 
        } 
    } 

So as I say, this works if Jack is asleep and I >untape Jack. But it doesn’t work if I, for example, >Push Jack (This gets “Jack probably wouldn’t like that.”)

Ideally, what I think I’d like to do is something like

dobjFor(gAction.ofKind(TouchTAction)) 
    { 
       "You tried to use a touchverb on Jack. "; 
    } 

But of course that won’t work because dobjFor is a macro that I only dimly understand.

In any case, and thinking more generally, there are lots of times when it is useful to catch all of a player’s touchverbs on a given object and handling them at once.

Any ideas?

Thanks in advance!

–Bob

I’ll poke around and see what I can figure out. As a side note, however, beforeAction will respond to any action carried out when the ch_jack object is in scope, NOT just actions directed at Jack. So when Jack is sleeping, you wouldn’t be able to touch anything in his presence, even if the code were working properly.

I think this works. No need to define your own Action class – just make a list of the actions you want intercepted.

+ harry: Actor 'Harry' 'harry' "He's lying here. " isHim = true isProperName = true beforeAction() { if (gActionIn (Kiss, Attack, Feel) && gDobj == harry) { "Not now -- he's sleeping. "; exit; } } ;
The response to ‘hit harry’, ‘kiss harry’, and ‘touch harry’ are what you need. You can add your own Untape action to the list. Of course I didn’t test whether Harry is asleep – that isn’t in my code. But it’s easy to add to the if-test.

Thanks, Jim. This works! (Your warning about the in-scope problem was spot on as well).

Still, I wish there were a more compact way to do this via creating a touchverb class so you don’t have to list all the touchverbs every time you want to do something like this. (There are more than 40 verbs that could reasonably be called touchverbs!)

The code in “Getting Started” hints tantalizingly that this kind of thing can be done:

actorAction() { if(isTiedUp && !gAction.ofKind(SystemAction) && !gActionIn(Look, Inventory, Examine) { "You can't do that while you're tied up. "; exit; } }

And as I say, my special class does work on verbs that are not already in the library.

But your code gets me over the hump for now, for which I am grateful.

For anyone interested in doing something like this, here is the list of verbs I identified in Eric The Unready as “touchverbs”

Attack, break, catch, climb, climb_down, climb_through, close, cut, drink, drink_from, eat, enter, feed, fill, kick, kill, kiss, knock, load, lift, move, open, overturn, peel, pet, pull, push, put_in, put_on, remove, shake, sit, squeeze, take, take_with, throw, throw_overboard, touch, turn, unlock, untie, wake, wear.

–Bob

I spoke too soon!

The system is still catching several verbs before my “beforeAction” handling is doing its work. In the following code, these verbs work as I would like: Attack, Kiss, Move, Pull, Push, Remove, Turn, Untape. But these verbs don’t and kick out a standard library message: Break, Climb (for the syntax >climb on) Close, Eat, Enter, Lift, Open, SitOn, StandOn, Take, Unlock.

ch_jack... beforeAction { if(gActionIn (Attack, Break, Climb, Close, Cut, Eat, Enter, Kiss, Lift, Move, Open, Pull, Push, Remove, SitOn, StandOn, Take, Turn, Unlock, Untape, ) && gDobj == ch_jack) //If the player tries to use a touchverb on jack { if (ch_jack.curState == jackSleeping) { // If Jack is asleep "Aww, he looks so cute while he's asleep. Why don't you leave the poor guy alone. "; exit; } } }

Also, should I add: else inherited after the if clause?

–Bob

This is because the action is considered illogical as applied to an Actor. The error message is produced by the verify() block in dobjFor(Break), etc. Here’s a simple fix:

+ harry: Actor 'Harry' 'harry' "He's lying here. " isHim = true isProperName = true dobjFor(Break) { verify() { logical; }; } dobjFor(Climb) { verify() { logical; }; } beforeAction() { if (gActionIn (Kiss, Attack, Feel, Break, Climb) && gDobj == harry) { "Not now -- he's sleeping. "; exit; } } ;
Note, however, that if you do it this way you’ll also need to edit all of the dobjFor Actions to handle what happens if he’s NOT asleep. Better would be something like this:

dobjFor(Break) { verify() { if (isAsleep) logical; else inherited(); } }
To answer your question – no, I don’t think you need an else inherited in the beforeAction. By default, beforeAction does nothing, so there’s nothing to inherit.

To my way of thinking, the code above is actually preferable to creating your own class of Actions. If you do that, you’ll have to write a modify block of code for each action you want to modify. This code will be in a separate place. The way I’m doing it is less elaborate, and all of the code for the NPC will be tucked away in the NPC, where it’s easy to see and debug.

Also, as an added note, if you’re using ActorStates, which is highly recommended, your NPC will presumably have a jackAsleep ActorState. I believe you can put the beforeAction code in the ActorState, thus eliminating the need to test whether he’s asleep. You’ll still need to put the dobjFor(Break), etc., in the Actor object, however, so you might want to put the beforeAction there too, just to be tidy.

Almost there. Setting aside your suggestions about ActorStates, which I will have to work my way through, I’ve almost got your previous code working. The only verbs with which I’m having trouble are “Take” (which I modified in my own verbs file to include the player input “grab”), “Eat” (which does an implied take), and “Lift”, a verb I have created but whose default makes it act like Take.

Here is the code in my verbs file.

[code]// LIFT

DefineTAction(Lift);

VerbRule(Lift)
(‘lift’)
singleDobj
: TouchTAction, LiftAction
verbPhrase = ‘lift/lifting (what)’
askDobjResponseProd = singleNoun
;

modify Thing
dobjFor(Lift) asDobjFor(Take)
;

// TAKE

replace VerbRule(Take) //I have replaced this verb to add the synonym ‘grab’
(‘take’ | ‘pick’ ‘up’ | ‘get’ | ‘grab’) dobjList
| ‘pick’ dobjList ‘up’
: TouchTAction, TakeAction
verbPhrase = ‘take/taking (what)’
;
[/code]

Thanks for all your help!

–Bob

I am still having trouble with >Take, and it turns out that beforeAction is not even getting called when the player types >Take Jack, but the command is being intercepted by the check routine of Immovable and kicking out the “cannotTakeMsg.”

If I comment out gameMain.before RunsBeforeCheck = nil, then the code works. But “Getting Started” recommends that you do set it to nil, and as a noob it makes me nervous to go against Eric Eve’s advice.

So I thought I would just override the check with check () {}. But then if I type >Take Jack, the game says, “Taken”

So tried to override the check in the same way that I override verify

dobjFor(Take) { 
        verify() { if (ch_jack.curState == jackSleeping) logical; else inherited(); } 
        check () { if (ch_jack.curState == jackSleeping) logical; else inherited(); }
    }

That compiles, and when I >Take Jack before he is asleep, I get the correct response.
However, when I >Take Jack after he is asleep, the game crashes with TADS Runtime Error: nil object reference

So I am officially over my head (again) it’s time to ask for help.

–Bob

It’s fine to set gameMain.beforeRunsBeforeCheck to true, provided you know what you’re doing and you’re sure that it’ll always give the effect that you want (namely to make the before notifications run before the check routines). My advice in Getting Started probably reflects my own preference, namely that the order is more logical the other way round, but YMMV.

What all touch verbs have in common is the use of the touchObj precondition in their iobjFor and/od dobjFor precondition list. The neatest way to trap them then might be to modify the touchObj precondition.

The following is untested, but might point you in the right direction:

modify touchObj
   checkPreCondition(obj, allowImplicit)
   {
       inherited(obj, allowImplicit);

       /* If the target object doesn't want the source object to touch it
        *  fail the test.
        */
       if(obj.allowTouch(sourceObj) == nil)
         exit;
   }
;

modify Thing
    allowTouch(srcObj)
    {
        return true;
     }

Then on the Jack object you could do something like:

  allowTouch(srcObj)
  {
      if (curState == jackSleeping && srcObj == gPlayerChar) 
       { 
                "Aww, he looks so cute while he's asleep. Why don't you leave the poor guy alone. "; 
                return nil; 
       }       

       return true;
   }

This should block any attempt by the player character to touch Jack while he’s asleep.

Thanks, Eric. Busy day today so I won’t be able to test for several hours, but when I do, I’ll report back. Best, --Bob

To close this off - I have tested this and it works. I still need to understand it better and integrate it with other code, but thanks, Eric, for writing this up and for steering me in a good direction. --Bob

For anyone who might follow this in the future, today I found a much more elegant solution staring me in the face in Eric Eve’s “Learning TADS 3” book. On page 185 you’ll find a discussion of OutOfReach and creative ways to use it, and more specifically a way to test for what I call touchverbs.

bob: OutOfReach, Person 'bob/man*men' 'Bob'
isHim = true
isProperName = true
canObjReachContentsObj(obj) { return curState != bobDeadState; }
cannotReachFromOutsideMsg(dest) { return 'You don\'t like to touch a corpse!'; }
;
+ bobDeadState: HermitActorState
noResponse = "He\'s dead; he can\'t hear you. "
specialDesc = "Bob lies dead on the ground. "
stateDesc = "Bob is dead. "
;

Two things to watch out for here:

The first is that if you have already created your character as a Person, you need to remember to add the OutOfReach class to its definition.

The second is what I believe is a typo in the code. I think “canObjReachContentsObj(obj)” has an extra “Obj” appended after the word “Contents” and the line should instead read:

canObjReachContents(obj) { return curState != bobDeadState; }

Smarter heads than mine should verify this, but it works when I tested it.

In short - if you’re thinking about testing for touchverbs, remember to try OutofReach!