Messages
So far, when we've used things like illogical(), reportFailure() and the like, we've used them with single-quoted strings (e.g. illogical('You can't do that. ') ). If you look in library source code, however, you won't see them used like that; instead you'll see them used with property pointers (e.g. &cannotTakeMsg) sometimes followed by one or more further arguments (a property pointer is & followed by the name of the property we want to reference). This allows the text of messages to be kept separate from the library code, which, among other things, makes it easier to translate the library into another language, since all the language-dependent stuff is on one place - or rather two (en_us.t and msg_neu.t) - rather than scattered all over the library.
What actually happens when the library sees something like illogical(&cannotTakeMsg) is that it calls the corresponding property on the object returned by gActor.getActionMessageObj(); thus, for example, when the library wants to display the message from illogical(&cannotTakeMsg) it actually calls:
|
gActor.getActionMessageObj().cannotTakeMsg
|
|
illogical(cannotTakeMsg)
would look for a property called cannotTakeMsg
on the current object and pass the value of that property to whatever routine was going to process it. But that's not what we want here; what we want to do is to tell the routine which property of gActor.getActionMessageObj()
to use, and for that we need to pass a property pointer (&cannotTakeMsg
) not the property value (cannotTakeMsg
).
When the player character is the actor (as is generally the case when executing player commands), then
gActor.getActionMessageObj() returns playerActionMessages; if an NPC is performing the action then it returns npcActionMessages. Both objects are defined in msg_neu.t, where you can find all the library default messages.Sometimes you'll see in library code that one of these message macros has more than one parameter; for example, you might see something like:
|
illogical(&mustBeHoldingMsg, self);
|
|
|
gActor.getActionMessageObj().mustBeHoldingMsg(self)
|
|
Firstly, if you don't like any of the library default messages, you can simply modify the playerActionMessages object to substitute your own version (this is not the only way to do it, but it's probably as good as the alternative, which we'll see in a minute). For example, the standard response to trying to put something on an object that isn't a Surface is "There's no good surface on {the iobj/him}. "; thus, for example, if you try to put the stick on the cottage:
|
There's no good surface on the pretty little cottage.
|
|
notASurfaceMsg = '{You/he} can\'t put anything on {the iobj/him}. '
;
|
In addition to this, you can also change the messages by defining the appropriate message property on a class or object. Thus, for example, we could have obtained almost the same result by modifying Thing:
|
notASurfaceMsg = '{You/he} can\'t put anything on {the iobj/him}. '
;
I say almost the same result because there is in fact a small (though readily fixable) catch with this that we'll come to shortly, which is why the first method might be slightly better for making global changes to messages. However, this second method is very useful when you want to customise the message that appears for individual objects (or particular classes of object). For example, suppose instead of the plain vanilla "You can't take that" message you'd get from trying to take the cottage, you'd like to see "It may be a small cottage, but it's still a lot bigger than you are; you can't walk around with it!" One way to do that would be to override the cottage's verify method for the Take action:
|
{
verify()
{
illogical('It may be a small cottage, but it\'s still a lot
bigger than you are; you can\'t walk around with it! ');
}
}
|
|
lot bigger than you are; you can\'t walk around with it! '
|
But although this is very useful, it also presents a potential trap for the unwary. Suppose we also wanted to customise the response to clean cottage. In the same way we could just add a cannotCleanMsg property to the definition of the cottage:
+ Enterable -> (outsideCottage.in)
'pretty little cottage/house/building' 'pretty little cottage'
"It's just the sort of pretty little cottage that townspeople
dream of living in, with roses round the door and a neat
little window frame freshly painted in green. "
cannotTakeMsg = 'It may be a small cottage, but it\'s
still a lot bigger than you are; you can\'t walk around with it! '
cannotCleanMsg = 'You don\'t have time for that right now. '
;
|
|
cannotCleanMsg = dobjMsg('You don\'t have time for that right now. ')
|
|
|
notASurfaceMsg = iobjMsg('You can\'t reach the roof. ')
|
|
There is one further problem here. If you type a nonsensical command like clean cottage with door you'll now see the response "You don't have time for that right now", which is clearly less than ideal. This can't be fixed simply by tweaking message properties; the cleanest solution here might be to make clean with fail in check rather than verify on the direct object, so the indirect object's failure message is used instead:
|
dobjFor(CleanWith)
{
verify() {}
check() { failCheck(&cannotCleanMsg); }
}
;
|
PutIn Thing iV notAContainerMsg
Fixture dV cannotPutMsg
Component dV cannotPutComponentMsg(location)
Immovable dC cannotPutMsg
This means that Thing.verifyIobjPutIn uses notAContainerMsg, and this will propagate all the way down the class hierarchy (except for objects that are Containers, of course). There's no entry for Thing dV since in general there's no reason to rule out a Thing as the direct object of a PutIn command. However, since Fixtures, Components and Immovables can't be moved, they can't be put in anything, so there are messages for not being able to put them. The only difference between Fixture and Immovable is that in Fixture a PutInAction is ruled out in verify(), whereas in an Immovable it's ruled out in check(); in both cases the cannotPutMsg is used. A Component also rules itself out as the direct object of a PutIn command, again in verify(), but this time with a different message and one that calls a parameter (location will normally be the object the Component is a component of). If you wanted to define your own cannotPutComponentMsg on an object, you can either simply define it as a single-quoted string, or as a method that's passed location as a parameter, e.g. either
|
the worble-wangler. '
|
Or
|
|
{
gMessageParams(obj);
return 'You can\'t do that, because it\'s part of {the obj/him}. ';
}
|
|
"The forest is off to the east. "
tooDistantMsg = 'It\'s too far away for that. '
;
|
Getting Started in TADS 3
[Main]
[Previous] [Next]