In the course of creating my first (and as yet, only) TADS game I came across a number of pitfalls, gotchas, aha’s, etc. I was too stressed at the time to systematize them and benefit the public with my discoveries, but I’m going to make an attempt to do so now.
Perhaps at some point these can be better catalogued and maybe make their way into a cookbook somewhere (@jnelson ?), but at least for now, I am going to dump them here in the random order that I encounter them as I go back and peruse my files.
I grant that many of these observations may have only a small window of applicability or interest, but for the greater good of TADS I’m going to just dump anything I learned, so I hope somebody can find something useful!
I happen to be sick right now with some unforeseen time on my hands, and that’s why I have time to get this started. Presuming that I can find the time, I hope to make succeeding posts in this thread with more dumps as I slowly wade through my source code and notes files.
This first post is but a nibble of all that may potentially come after, but I’m going to post it now just so I can see the thing started.
(I’m afraid that many, if not most of these will be adv3 specific, but there may be a little bit of Lite overlap.)
Thanks!
RANDOM TADBITS #1
Text printed in afterAction will suppress defaultReports. Standard TAKE, DROP, PUT responses, among several other verbs, and acknowledgments of entering or leaving NestedRooms are all wrapped in defaultReport.
This being the case, if you have "Somewhere, a bell rings. " written in an in-scope afterAction, you will end up with:
>take kiwi
Somewhere, a bell rings.
To avoid this, wrap everything printed in an afterAction in an extraReport macro (unless the message is meant to supplant any possible defaultReports!)
If you use ofKind in a static expression of a class or modify Class property, beware that ofKind will only take into consideration the superclasses of the class you are then dealing with! For instance, this won’t work:
modify Thing
isCombustible = static ofKind(WoodenItem) || ofKind(ClothItem)
;
because Thing is not a WoodenItem. One way to get around this would be:
modify Thing
isCombustible = nil
initializeThing() {
inherited();
if(ofKind(WoodenItem) || ofKind(ClothItem))
isCombustible = true;
}
;
Out of the box, you can’t define cannotGoThroughClosedDoorMsg on a Door instance to override the default message, the way you can modify most "cannotVerbMsg"s. You have to
modify playerActionMessages
cannotGoThroughClosedDoorMsg(door) {
if(door==myDoor) return 'My message. ';
else return inherited(door);
}
Alternatively, you could
modify Door
cannotTravel {
if (gActor.canSee(self) && !isOpen) {
if(propDefined(&cannotGoThroughClosedDoorMsg))
reportFailure(cannotGoThroughClosedDoorMsg);
else
reportFailure(&cannotGoThroughClosedDoorMsg, self);
}
else inherited();
}
;
and then you could define the messages on each Door object.
Printed text (without conditions) in a beforeAction or roomBeforeAction will appear twice (or more, if you have accompanying Actors!) when the action is directional travel. This is because of the way the library handles travel: there is a TravelAction involved as well as a TravelViaAction. Make sure your text is surrounded by the proper “if’s” in order to be printed.
One of the most elementary lessons for a TADS learner is to remember to call inherited when overriding methods (if you are adding statements rather than replacing the method). Be particularly aware in the situation of creating a Thing-based class and modifying the construct method… if you don’t call inherited the object won’t respond to any vocab words! The normal constructor calls initializeThing which gives vocabulary to the object.
class Apple: Thing 'apple*apples' 'apple' "It's a <<variety>> apple. "
varieties = ['Winesap','Jonathan','McIntosh']
variety = nil
construct {
local v = rand(varieties);
variety = v;
initializeVocabWith(v);
}
;
// Yikes! Without "inherited", we won't be able to refer to these as "apple"!
The order in which pre-room-title reports appear:
.1. Implicit action announcements
2. Sidekick NPC implicit action announcements
3/4. PushTravel messages
3/4. Messages wrapped in reportBefore
?. GuidedInTravelState.sayDeparting
5. accompanying actor npcTravelDesc
6. travelDesc
7. enteringRoom (text printed within)
I didn’t rigorously test this, but I suspect that PushTravel messages and reportBefore messages will appear in the reverse order that they are reached through code execution. For instance, if a reportBefore occurs both in dobjFor(TravelVia) and enteringRoom in the same command, the message from enteringRoom will appear first because the code reached it last.
roomDaemon is responsible for calling a Room’s atmosphereList. It can be handy, however, to monitor or control other things as well. But one pitfall to avoid is assuming that a given location’s roomDaemon is called on every turn of the game! The only roomDaemon which is called on a given turn is the one for the PC’s location. Therefore roomDaemon is not suitable for monitoring or tracking info about a location that needs to happen in the background as well, i.e. the PC is not there.
If you have an Actor and you want to customize their obeyCommand method, be aware that gDobj is not in effect yet in the action execution cycle. In order to make statements conditional upon the direct object, you need to use getResolvedDobjList, thus:
obeyCommand(issuingActor, action) {
if(action.ofKind(SomeAction) &&
action.getResolvedDobjList().indexOf(someObject))
return true;
else return inherited(issuingActor, action);
}
It can be a surprise to find that when you customize an Actor’s specialDesc, your custom desc is not used if that Actor is in a NestedRoom. This is because the call is triggered by showSpecialDescInContents and not the basic showSpecialDesc. For regular Things, showSpecialDescInContents does call their normal specialDesc but for Actors, the method calls listActorPosture. Therefore you have to override one of the involved methods to get the “specialDesc” you want when the Actor is in a NestedRoom.