Pulling a Wagon (adv3Lite)

Looking through the docs, I don’t find any hints on how to do what I need. I have a little red wagon that I want the player to be able to pull (but not push) from one room to another. Even if I wanted to allow pushing as well as pulling, I don’t know how to set that up either.

The wagon is not a vehicle – you can’t get into it. It’s just a container. So the section of “Learning T3Lite” on Vehicles doesn’t seem to apply. (Also, you can’t pick it up. You have to pull it to take it anywhere.)

This is the default output:

This output results from a PushTravelDir action. Note that pushing and pulling are considered synonymous, which is not what I want. But even if I liked pushing, I don’t know how to make the action work. There’s no allowPush property to set to true. I set isVehicle to true, but that didn’t help. I’m sure there’s a simple solution, but I’m baffled. Suggestions would be appreciated!

If you were prepared to allow ‘push’ as a synonym for ‘pull’ here the solution would be simple; simply define canPushTravel = true on your little red wagon.

If you really need to distinguish ‘push’ from ‘pull’ in this situation, then to do it “properly” would, I’m afraid, require a lot of extra work, since this is something the library doesn’t cater for.

First, you will need to go through all the VerbRules that have names starting with PushTravel (such as PushTravelClimbUp) and modify them so that they no longer refer to words like ‘pull’ and ‘drag’. You will also have to decide whether you want ‘move’ to mean ‘push’ or ‘pull’. For example:

VerbRule(PushTravelClimbDown)
    ('push' | 'move') singleDobj
    :
;

And so on for all the rest.

Then you would need to copy and paste all the PushTravelXXX actions and VerbRules from actions.t and grammar.t into your own game code and rename them to PullTravelXXXX, as well as adjusting the VerbRules so they all match ‘pull’ and ‘drag’ rather than ‘push’.

Finally you would need to modify Thing with copy and pasted versions of all the dobjFor(PushTravelXXX) sections turned into dobjFor(PullTravelXXX) sections, as well as new versions of some of the supporting methods such as verifyPushTravel() adapted to verifyPullTravel(). You may also need to search for iobjFor(PullTravelXXX) methods on other classes and do the same.

In other words, this is one heck of a lot of work just to get PUSH WAGON EAST and PULL WAGON EAST to mean different things.

There are, however, a couple of potential shortcuts you can look at which could save you this enormous amount of effort.

First, if there are no objects in your game that you want to be able to push anywhere, you can simply adapt the existing PushTravel framework to act as PullTravel. To do this you would first modify all the VerbRules mentioned above to accept only ‘pull’ or ‘drag’ but not ‘push’. You would then override all the message properties on Thing that relate to PushTravel (look for the verifyPushTravel() method on Thing and find the messages that follow) to refer to pushing rather than pulling. You’d still need to define canPushTravel = true on the wagon, of course; effectively you’d still be using the various PushTravel actions but you’d be making them look like PullTravel ones.

Or second (and this is what I would do), simply define canPushTravel = true on the wagon and override its various PushTravel message properties so that they refer to pulling rather than pushing. Forcing the player to type PULL WAGON EAST rather than PUSH WAGON EAST when both would otherwise end up doing the same thing is almost certainly more trouble than it’s worth [But see bellow for a possible kludge to handle this].
The two methods you’d most need to override on the wagon are beforeMovePushable(connector, dir) and describePushTravel(via), which might become (for example):

beforeMovePushable(connector, dir)
 {
        if(connector == nil || connector.ofKind(Room))          
          "{I} pull{s/ed} {the dobj} <<dir.departureName>>. "";
        else
            describePushTravel(viaMode);      
        
 }

describePushTravel(via)
    {
        /* If I have a traversalMsg, use it */
        if(gIobj && gIobj.propType(&traversalMsg) != TypeNil)           
           "{I} pull{s/ed} {the dobj} <<gIobj.traversalMsg>>. "
        else
           "{I} pull{s/ed} {the dobj} <<via.prep>> {the iobj}. ";
    }

Alternatively, you might define a CustomMessages object that only takes effect when the direct object is your wagon:

CustomMessages
   active = (gDobj == redWagon)

   messages = [
      Msg(before push travel dir, '{I} pull{s/ed} {the dobj} {1}. '),
      Msg(push travel traversal, '{I} pull{s/ed} {the dobj} {1}. '),
      Msg((push travel somewhere, '{I} pull{s/ed} {the dobj} {1} {the iobj}. ',)
  ]
;

You might need to do a bit more than this to get every reference to pushing changed to pulling on the wagon, but that should be enough to point you in the right direction.

Finally, there may be a slightly kludgy way to refuse the command PUSH WAGON EAST (etc.) while still using the second and simpler option above. I think you may be able to look at gCommand.verbProd.tokenList to see what the player typed. That being so, it may be possible to do something like this on your wagon:

  verifyPushTravel(via)
  {     
      if(getTokVal(gCommand.verbProd.tokenList[1]) == 'push')
         implausible('{The subj dobj} {can\'t} be pushed anywhere. ');

      inherited(via);
  }

Interesting – thanks for the detail. I’m sure I’ll be able to work out something based on your suggestions.

I can see why it would be fairly pointless for the library to differentiate between pushing and pulling objects compass directions. It would be a lot of extra code, and most authors would never need it. Even so, the two actions are physically distinct. If I’m feeling ambitious, I might try writing a push-pull extension that would separate the two actions. But not this week.

In the meantime I’ve just added a gVerbName macro to the adv3Lite library which would make it easy for game authors to determine whether the player had typed PUSH THING DIRECTION or PULL THING DIRECTION (gVerbName would then be ‘push’ or ‘pull’ respectively). Armed with that dealing separately with pulling and pushing may become quite straightforward unless you want PUSH OBJECT DIR and PULL OBJECT DIR to actually do something different on the same object.

Armed with this new macro, you can then define a CustomMessages object that changes ‘push’ to ‘pull’ in the description of PushTravel if the player typed ‘pull’ or ‘drag’ to describe the action:

CustomMessages
    active = (gVerbWord is in ('pull', 'drag'))
    messages = [
      Msg(before push travel dir, '{I} pull{s/ed} {the dobj} {1}. '),
      Msg(push travel traversal, '{I} pull{s/ed} {the dobj} {1}. '),
      Msg(push travel somewhere, '{I} pull{s/ed} {the dobj} {1} {the iobj}. ',)
  ]
;

Finally you could modify Thing along the following lines:

modify Thing
    canPushTravel = nil
    canPullTravel = canPushTravel
    
    verifyPushTravel(via)
    {
        viaMode = via;
        
        if(gVerbPhrase == 'push' && !canPushTravel)
            implausible(cannotPushTravelMsg);
        
        if(gVerbPhrase is in ('pull', 'drag') && !canPullTravel)
            implausible(cannotPushTravelMsg);
        
        if(!canPushTravel && !canPullTravel)
            illogical(cannotPushTravelMsg);
        
        if(gActor.isIn(self))
            illogicalNow(cannotPushOwnContainerMsg);
        
        if(gIobj == self)
            illogicalSelf(cannotPushViaSelfMsg);            
        
    }
    
    cannotPushTravelMsg()
    {
        if(isFixed)
            return cannotTakeMsg;
        return BMsg(cannot push travel, 'There{dummy}{\'s} no point trying to
            {1} {that dobj} anywhere. ', gVerbWord);
    } 
;

This would need a bit more work to cater fully for commands like PUSH WAGON UP STAIRS or PULL WAGON THEOUGH DOOR, and as a solution it’s a bit language-specific, but it’s a start.

While i don’t have a current need to pull a wagon, I feel your pain. Push wagon and pull wagon just don’t seem to me to be synonymous, and I wouldn’t want to compromise that way if I were writing it. But it also seems to me that any one of Eric’s suggestions represents a big bunch of work.

Just riffing intuitively, it seems to me this might be a good use case for a Doer coupled with your own custom PullWagon action.

Obviously, I haven’t worked out all the details that I’m sure will arise during the implementation and since I don’t have need of a pull wagon action, I don’t have any code to share. YMMV, but it seems to me the work involved would be somewhat less than any of Eric’s proposed solutions and, if it were my problem, I think that would be my first avenue of exploration.

Jerry

Personally, I doubt that’s the way to go. A Doer is good for stopping an action, or for turning one action into another action. It’s not a good way to define what a new action does in its entirety, since you’re liable to miss out too many of the steps that the Action class and its subclasses take care of for you.

Well, there are many ways to get there from here. It weems to depend to a large degree on what is the objective.

Sort of depends on what the new action is and what its entirety covers, doesn’t it?

You appear to have had a change of opinion on Doers since you wrote this in the Adv3Lite Manual…

If all the wagon does is cart an ice chest full of beer from place to place, then it doesn’t seem to need a lot of the handholding provided by the Action class. Some judiciously placed and worded catchall traps and travel barriers could cover for a lot of the steps provided by the library.

I do recognize that there will be a number of contingencies that must be anticipated in the Doer, but the question is, which is more tedious and error prone—anticipating those contingencies and then testing the game play and putting out brush fires as they erupt with new Doer code as necessary, or combing through the library and duplicating all the implausible and cannot messagets and having to find all of the PushTravels and define new PullTravels?

Seems like either solution will require some due diligence and post-testing remediation, the question is which approach seems to achieve the desired result with the least amount of tedium and lowest error rate?

Without knowing Jim’s ultimate objective, it’s pure speculation, of course, but I think for what I can envision using such a wagon for, I’d still favor the Doer approach, at least until I got in too deep and had to swim back to shore.

Jerry

I really don’t see that a Doer would make things any easier here.

Your PullTravel has to handle a number of different actions:

PULL WAGON EAST
PULL WAGON THROUGH DOOR
PULL WAGON UP SLOPE
PULL WAGON DOWN STAIRS
PULL WAGON INTO HOUSE

Then, for each of these you have to make sure that both the actor and the wagon travel to the new location, but that travel barriers that might prevent either of them from traveling are honoured. You have to ensure that the action is correctly reported both as the wagon leaves the old new location and as it appears in the new one. If the wagon has anything concealed beneath it/behind it, this has to be revealed when it’s moved. If some object wants to object to the travel you have to ensure that the beforeAction and beforeTravel notifications are properly handled. And that’s just all off the top of my head.

Handling PushTravel (and hence PullTravel) properly is incredibly complicated. It’s about the most difficult and complex set of actions to implement. That complexity is probably what’s making adapting PushTravel to separate out ‘pull’ from ‘push’ look so complex; there’s a lot to handle. But most of the work is already done in the various PushTravel actions already defined in the library together with their supporting functions. Trying to start from scratch in a Doer, ignoring the action framework that’s already there, would be an order of magnitude more complicated, and much more error prone. There may be quite a lot of work following the suggestions I’ve made to Jim, but it’s relatively straightforward workv by comparison, and mainly cosmetic in nature (and I may look at incorporating it into the library).

A Doer really isn’t the right tool to tackle this issue. It simply isn’t.

And I’ve now made a start, with where I’ve got so far uploaded to GitHub.

To make something pullable (from room to room) rather than pushable, define its canPullTravel property as true. It will then respond to commands like PULL WAGON EAST but will disallow PUSH WAGON EAST. In some cases it will also report correctly that the wagon (or whatever the object in question is) has been pulled rather than pushed, but I need to do more work on the messages before this part is complete.

Everything still uses the PushTravel action under the hood; the changes are largely cosmetic.

This seems to be working well. Thanks, Eric! I’m able to pull (but not push) the little red wagon, and I’ve added a TravelBarrier to a flight of stairs, as of course I don’t want the player to be able to pull it up or down stairs. (Yes, this is part of a puzzle.)

I haven’t tried using gVerbName yet. I can think of a few cases where that may be handy – for customizing check() messages, for intance.

I’m glad it’s working well for you, but I’m not sure if “this” refers to one of my suggestions earlier in the thread or the code posted to GitHub. In case it’s the latter I should let you know that I’ve now posted an updated version there that completes the handling the push/pull travel distinction.

I was referring to the github code. I’ll download the newer version tonight.