The ZIL manual lists two ways for setting up ASK/TELL conversations:
- The (EVERYTHING) syntax addition allows for verb definitions where any object is accepted, whether or not it is scope. Like so:
<SYNTAX ASK OBJECT ABOUT OBJECT (EVERYTHING) = V-ASK> - It also mentions GENERIC-OBJECTS objects that are out of scope and can't be physically interacted with. It specifically mentions Deadline's "new will," the missing mystery document a player might be inquiring about. Despite not being physically available, these objects are always in scope.
Originally, I thought was a little unclear why ZIL needed both methods, as I figure that if the (EVERYTHING) syntax works, you could just stick those objects in a room no one will find them and, hey, no one will interact with them. It occurred to me while writing this, though, that an author might want different error messages for trying to interact with tangible objects compared to idea/topic objects. You don't want >EXAMINE FREEDOM to respond with "You don't see that here." Unless your game is kind of an ass about freedom.
Anyhow, the current ZIL/ZILF compiler doesn't support the (EVERYTHING) method, so it was recommended to me that I try to handle everything with GENERIC-OBJECTS. I imagined this was going to be sort of a headache. I figured I'd have to write fancy code to take GENERIC-OBJECTS out of scope when they had a real object counterpart within scope; if I had a flashlight in my game, I'd have to have a GENERIC-OBJECTS object for it for when players ask NPCs about it when it is another room, but when it is in scope, I don't want "Which flashlight do you mean, the flashlight or the flashlight?" responses. This ended up not being a problem, but I'll get to that later.
My lack of enthusiasm to play around with all of this resulted in a several month break from ZIL code, but I got the itch back recently. Of course, it didn't help that I initially got off on the wrong foot because I had forgotten the name of the scope thing I needed so I just quickly skimmed the ZIL model, saw another type of scope object called LOCAL-GLOBALS, and thought, ah, that must be it. Unfortunately, LOCAL-GLOBALS objects are something else, something better used for tangible things that show up in several rooms like a river or something.
After wasting a good couple hours on that, Jesse McGrew set me off on the right GENERIC-OBJECTS path. Right away, my disambiguation worries became a non-issue because it turns out GENERIC-OBJECTS are only parsed at a last resort so they never have the same "parser weight" as an actual object in the same room.
The only remaining issue was that while interacting with a non-present object properly responded with "You don't see that.", the message took up a turn. Consistency among error messages is one of my many goals with any IF language I work with.
Luckily, Jesse was there to the rescue again and pointed out the "hook" where we could fix that. He gave me this code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<REPLACE-DEFINITION HOOK-AFTER-PERFORM | |
<DEFMAC HOOK-AFTER-PERFORM (R-ATOM) | |
'<COND (<OR <AND <NOT <VERB? ASK TELL>> | |
<OR <IN? ,PRSI ,GENERIC-OBJECTS> | |
<IN? ,PRSO ,GENERIC-OBJECTS>>> | |
<AND <VERB? ASK TELL> | |
<IN? ,PRSO ,GENERIC-OBJECTS>>> | |
<SETG PRSA ,V?INVENTORY>)> | |
>> |
The interesting thing about this code is, as you can see by the name, it runs after the command has been performed but we still have time to say, "change the command to INVENTORY" (so it doesn't take a turn). This is another one of the things I find endearing about ZIL; an impressive amount of the game loop can be modified through code (unlike many later IF languages where more aspects of the game loop are set in stone by the game engine).
Jesse wrote the above code without testing. It probably worked just fine right out of the box, but as far as I could read it, I thought it seemed a little wrong so I instantly started re-writing it. It took several attempts, but I eventually got something that worked:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<REPLACE-DEFINITION HOOK-AFTER-PERFORM | |
<DEFMAC HOOK-AFTER-PERFORM (R-ATOM) | |
'<COND (<OR <AND <NOT <VERB? ASK TELL>> | |
<OR <IN? ,PRSI ,GENERIC-OBJECTS> | |
<IN? ,PRSO ,GENERIC-OBJECTS>>> | |
<AND <VERB? ASK TELL> | |
<IN? ,PRSO ,GENERIC-OBJECTS>>> | |
<SETG PRSA ,V?INVENTORY>)>>> |
So yes, I had to add some code to check for 0 values. My final code ended up looking like this:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<REPLACE-DEFINITION HOOK-AFTER-PERFORM | |
<DEFMAC HOOK-AFTER-PERFORM (R-ATOM) | |
'<COND ( <NOT <0?, PRSO>> | |
<COND ( <NOT <0?, PRSI>> | |
<COND (<AND <NOT <VERB? ASK TELL>> | |
<IN? ,PRSI ,GENERIC-OBJECTS>> | |
<SETG PRSA ,V?INVENTORY>)>)> | |
<COND (<OR <AND <NOT <VERB? ASK TELL>> <IN? ,PRSO ,GENERIC-OBJECTS>> | |
<AND <VERB? ASK TELL> <IN? ,PRSO ,GENERIC-OBJECTS>> | |
> | |
<SETG PRSA ,V?INVENTORY>)> | |
)> | |
>> |
Originally, I tried to spread out my code a bit more, but the way these DEFMAC things seem to work, they really only allow for one element. That's why my solution basically is one conditional with several other conditionals wrapped inside it. I asked Jesse about it, and it turns out there's another command, BIND, which allows for routines-within-routines. Not only that, but the BIND code can have its own local variables... and not only that, but you can also give those local variables starting values, something you can't do with "AUX" variables for your random routine.
In the code:
<BIND ((A 1) (B 2) Z T W) ...>
A would have a starting value of 1 and B would have a starting value of 2.
Anyhow, here is a final version of the above routines, using BIND to make the code easier to read (hopefully):
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<REPLACE-DEFINITION HOOK-AFTER-PERFORM | |
<DEFMAC HOOK-AFTER-PERFORM (R-ATOM) | |
'<BIND (ERR) | |
<COND (<NOT <0?, PRSI>> | |
<COND (<AND <NOT <VERB? ASK TELL>> <IN? ,PRSI ,GENERIC-OBJECTS>> | |
<SET ERR 1>)>)> | |
<COND (<AND <NOT <0?, PRSO>> <EQUAL? .ERR 0>> | |
<COND (<IN? ,PRSO ,GENERIC-OBJECTS> | |
<SET ERR 1>)>)> | |
<COND (<EQUAL? .ERR 1> | |
<SETG PRSA ,V?INVENTORY>)> | |
> | |
>> |
EDIT: Okay, it turns out I wasn't done with the above code. I was going through all of parser.zil's verb routines making sure that applicable routines gave "You don't see that." type responses for GENERIC-OBJECTS. I was reminded that >THINK ABOUT [object] is a default verb there, which of course should be allowed for taking up a turn. In testing out the code, I noticed that behavior would be broken after a couple turns. I had thought BIND treated its variables as local variables if not given a starting value, but no, they act more like global variables (so, ERR, once set as 1, would always stay 1). Here is the fixed code:
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<REPLACE-DEFINITION HOOK-AFTER-PERFORM | |
<DEFMAC HOOK-AFTER-PERFORM (R-ATOM) | |
'<BIND ((ERR 0)) | |
<COND (<NOT <0?, PRSI>> | |
<COND (<AND <NOT <VERB? ASK TELL>> <IN? ,PRSI ,GENERIC-OBJECTS>> | |
<SET ERR 1>)>)> | |
<COND (<AND <NOT <EQUAL? ,PRSO 0>> <EQUAL? .ERR 0>> | |
<COND (<AND <IN? ,PRSO ,GENERIC-OBJECTS> <NOT <VERB? THINK-ABOUT>>> | |
<SET ERR 1>)>)> | |
<COND (<EQUAL? .ERR 1> | |
<SETG PRSA ,V?INVENTORY>)> | |
> | |
>> |