Everything in its place
Starting Out Right
In the original version of the Goldskull game, which we began by reproducing, the rock starts out inside the cave, conveniently waiting to be placed on a pedestal. In a real game you probably wouldn't want to make the solution to the problem quite so obvious. In this two-room game the only other place to put the rock (unless we want to hide it somewhere, which we won't attempt here) is in the starting location. While we're at it we may as well move the pebble there too. We'll also introduce a new property, the initSpecialDesc. This determines the way an item is first described in a room listing, until the item is moved. Try moving the rock and the pebble into startroom and adding an initSpecialDesc to the rock like this:
+ smallRock: Thing 'small rock; round solid' "It's roughly round and looks pretty solid. " initSpecialDesc = "A small rock lies on the ground near the mouth of the cave, evidence, perhaps, of long-term erosion. " weight = 10 ;
Once you've made these changes, try recompiling and running the game. You should now see the following as the description of the starting location on the first turn:
Outside Cave You’re standing in the bright sunlight just outside of a large, dark, foreboding cave, which lies to the north. The path back to your camp winds roughly southeast through the dense foliage. A small rock lies on the ground near the mouth of the cave, evidence, perhaps, of long-term erosion. You can see a tiny pebble here.
This is arguably both better and worse than just displaying "You see a small rock and a tiny pebble here." It's better insofar as the longer sentence about the small rock is more atmospheric, but worse in that it then makes the sentence about the tiny pebble look just a little incongruous. What would we be even better would be if we could combine the mention of both objects into a single sentence. Here's one way we can do it:
+ smallRock: Thing 'small rock; round solid' "It's roughly round and looks pretty solid. " initSpecialDesc = "A small rock <<if pebble.moved>>lies <<else>> and <<mention a pebble>> lie <<end>> on the ground near the mouth of the cave, evidence, perhaps, of long-term erosion. " weight = 10 ;
Let's look at how this works. The smallRock will use its initSpecialDesc until it has been moved. When a Thing is moved (via a call to actionMoveInto(), which is generally used in the handling of any player command that causes an object to be moved) its moved property is set to true. We can thus use pebble.moved to test whether the pebble has been moved or whether it's still resting by the mouth of the cave next to the rock. If the pebble has been moved, then smallRock.initSpecialDesc just describes the rock as lying there, but if the pebble hasn't been moved yet it mentions the pebble as well.
The subtle touch is using the embedded expression <<mention a pebble>> to display the text 'a pebble' in the course of this description. What this does is not only to tell the game to provide the name of the pebble with the indefinite article ('a pebble') but also to note that the pebble has already been mentioned in the room description so it doesn't need to be mentioned again, thus suppressing the additional line "You see a small pebble here." Once either the pebble or the rock is moved, this changes, as the following transcript illustrates:
Outside Cave You’re standing in the bright sunlight just outside of a large, dark, foreboding cave, which lies to the north. The path back to your camp winds roughly southeast through the dense foliage. A small rock and a tiny pebble lie on the ground near the mouth of the cave, evidence, perhaps, of long-term erosion. >take pebble Taken. >l Outside Cave You’re standing in the bright sunlight just outside of a large, dark, foreboding cave, which lies to the north. The path back to your camp winds roughly southeast through the dense foliage. A small rock lies on the ground near the mouth of the cave, evidence, perhaps, of long-term erosion. >take rock Taken. >l Outside Cave You’re standing in the bright sunlight just outside of a large, dark, foreboding cave, which lies to the north. The path back to your camp winds roughly southeast through the dense foliage. >drop all (first taking off the knapsack) You drop the tiny pebble, the small rock and the knapsack. >l Outside Cave You’re standing in the bright sunlight just outside of a large, dark, foreboding cave, which lies to the north. The path back to your camp winds roughly southeast through the dense foliage. You can see a small rock, a knapsack, and a tiny pebble here.
You may find this combination of techniques useful for getting your room descriptions starting out just the way you want.
initSpecialDesc has a close relative called specialDesc, the difference being that specialDesc displays all the time (whether the object it has been defined on has been moved or not — actually that's not strictly true since you can control when both specialDesc and initSpecialDesc are used, but that's a complication we'll leave aside for now). Both initSpecialDesc and specialDesc are used regardless of the value of isFixed or isListed on the item in question, so a common use of specialDesc is to provide a separate description of a fixed object not otherwise mentioned in the main room description (particularly when we want to call attention to the object rather than letting it merge into the overall description of the room).
Consider the description of the cave room. As it now appears (with the rock and pebble moved outside), the player will see:
Cave You’re inside a dark and musty cave. Sunlight pours in from a passage to the south. On the stone pedestal you see a gold skull.
This is actually a little odd, since the stone pedestal is only mentioned as the supporter of the gold skull, yet it's referred to as 'the stone pedestal' as if the player already knows about it. Morever, if nothing were resting on the pedestal, it wouldn't be mentioned at all (admittedly that situation can't come about in this game without killing the player, but it still doesn't seem quite right in principle). Of course we could simply add a mention of the object to the main room description, but this seems quite a good opportunity to illustrate the use of specialDesc:
+ pedestal: Fixture, Surface 'stone pedestal; smooth solitary' "The smooth stone pedestal is artfully positioned to catch the sunlight at just this time of day. " specialDesc = "A solitary stone pedestal stands at the centre of the cave. " notifyRemove(obj) { gMessageParams(obj); if(getWeightWithin() - obj.getWeight < goldSkull.weight) { "As {the subj obj} {leaves} the pedestal, a volley of poisonous arrows is shot from the walls! You try to dodge the arrows, but they take you by surprise!"; finishGameMsg(ftDeath, [finishOptionUndo]); } } ;
The room description then becomes:
Cave You’re inside a dark and musty cave. Sunlight pours in from a passage to the south. A solitary stone pedestal stands at the centre of the cave. On the stone pedestal you see a gold skull.
This is probably an improvement. At least the text makes sense, and at least the stone pedestal will get a mention even if there's nothing on it, but it would be neater if the paragraph about the skull could be combined with the paragraph about the pedestal. We could try to do with some combination of <<if goldSkull.location==pedestal>> and <<mention a goldSkull>>, but since in principle all sorts of objects could end up on top of the pedestal, this approach would pretty soon become quite unwieldy. What we need is some means of listing all the objects currently on the pedestal, whatever they happen to be. The following should do the trick:
+ pedestal: Fixture, Surface 'stone pedestal; smooth solitary' "The smooth stone pedestal is artfully positioned to catch the sunlight at just this time of day. " specialDesc = "A solitary stone pedestal stands at the centre of the cave<<if listableContents.length > 0 >>, with <<list of listableContents>> resting on top of it<<end>>. " notifyRemove(obj) { gMessageParams(obj); if(getWeightWithin() - obj.getWeight < goldSkull.weight) { "As {the subj obj} {leaves} the pedestal, a volley of poisonous arrows is shot from the walls! You try to dodge the arrows, but they take you by surprise!"; finishGameMsg(ftDeath, [finishOptionUndo]); } } ;
We use listableContents rather than just contents to exclude any fixtures or components that are attached to the pedestal rather than just resting on top of it (see next subsection). If there are any items in the listableContents list (which we can test by seeing whether its length is greater than 0) then we append a list of what they are, in a suitably-worded clause. The embedded expression <<list of listableContents>> both produces a properly formatted list of whatever's in listableContents, it also marks everything in the list as having been mentioned so it won't appear in the room contents listing again. With this definition the room description becomes:
Cave You’re inside a dark and musty cave. Sunlight pours in from a passage to the south. A solitary stone pedestal stands at the centre of the cave, with a gold skull resting on top of it.
There are several other ways we can tweak how items are displayed in a room description listing. For the full story see the section on Room Descriptions in the adv3Lite Manual.
Giving Due Warning
As the game stands, it gives very little warning to the player that taking the gold skull might spring a trap. To be sure both the setting and the nature of the skull might suggest that a hidden danger is lurking somewhere, and the wary player might well suspect that just walking in and taking the skull seems a bit too easy; moreover, if the player character gets himself killed the player can always UNDO and try something else, but most modern players of Interactive Fiction tend to regard such "learning by dying" as rather bad form, and if you want your game to be respected, it's better to put some kind of clue or warning of impending danger that goes beyond relying on your players' intuition.
There are all sorts of ways we could do that here. If this were part of a larger game, the player could have been warned of the trap in a previous episode. Or we could furnish the cave with the grisly remains of the last adventurer who tried to steal the gold skull. Or we could call attention to rows of suspicious-looking holes in the walls of the cave. But what we'll actually do here is provide the pedestal with a warning inscription:
+ pedestal: Fixture, Surface 'stone pedestal; smooth solitary' "The smooth stone pedestal is artfully positioned to catch the sunlight at just this time of day, picking out the inscription carved into its front. " ... ; ++ Fixture 'inscription; faded ancient sumerian; lettering script cuneiform' "The lettering is quite faded, and the script is in ancient Sumerian Cuneiform, but fortunately that's a language you took the trouble to learn, and you can just about make it out. " readDesc = "The inscription reads <q>Whoever dares remove this sacred object from its place shall become as that which he desires.</q> " weight = 0 cannotTakeMsg = 'You can hardly take the inscription; it\'s part of the pedestal. ' ;
For the sake of brevity we've omitted repeating the rest of the definition of the pedestal. Note that we don't need to give the inscription object a programmatic name, since no code ever needs to refer to it; it's only one step up from a decoration. We do define a custom cannotTakeMsg, however, since it seems better to say that the inscription is part of the pedestal than to say merely that it's fixed in place (customizing message properties like this is a good way to give your game some character and to show that you've taken trouble with it; if all players see is lots of default messages they probably won't think much of your efforts). We define weight = 0 because we don't want the inscription to contribute to the weight of objects resting on the pedestal, as it otherwise would. Note that since we took the trouble to use listableContents rather than contents above to list what's on the pedestal, we've averted the danger of the inscription showing up as one of the things resting on the pedestal (as it otherwise would). It might have been easier to make the inscription a completely separate object, not contained in the pedestal at all, and since neither the pedestal nor its inscription are ever going to move in this game, that would have worked just fine. In other situations though, it's possible that something like the pedestal might be moved, so it's best to maintain the containment relationship if possible, to make sure its components (such as the inscription) will always move with it. It also serves to make the relationship between the inscription and the pedestal that much clearer in your code. Note, however, that if we wanted to attach a component to a Container we might need to take a different approach, because if the Container were ever to become closed (or started out closed), the component would be hidden away out of sight inside the Container. We'll return to this issue in a later chapter.
<q> and </q> just give a pair of matching typographical quotes (“ and ”). Of more note is the readDesc property, which we haven't met before. If defined on an object, this gives a response to the command READ WHATEVER (in this case READ INSCRIPTION), which in general is different from the response to EXAMINE WHATEVER. If you did want the two to be the same you could just define readDesc = (desc).
Pointing the Way
We'll just perform one more task before finally leaving the Goldskull game. You may remember above we said that the way we'd set up the PathPassage notionally leading back to the camp was something of a horrible hack. Now we'll see how it should be done:
startroom: Room 'Outside Cave' desc = "You're standing in the bright sunlight just outside of a large, dark, foreboding cave, which lies to the north. The path back to your camp winds roughly southeast through the dense foliage. " north = cave in asExit(north) southeast = pathBack ; + pathBack: PathPassage 'overgrown path' "The heavily overgrown path runs roughly southeast through the trees. " canTravelerPass(traveler) { return goldSkull.isIn(traveler); } explainTravelBarrier(traveler) { "You've no intention of leaving till you've got what you came for. "; } travelDesc() { "Your mission complete, you head triumphantly back to your camp. "; finishGameMsg(ftVictory, [finishOptionUndo]); } ;
If you think there's something vaguely familiar about this, then you're right. For one thing, much of the code on the pathBack object was previously what was defined on the southeast() method of startroom. For another, pointing a direction property to an object and then defining the travelDesc property/method on that object is something we've done before, when we made the tree object in the Heidi game a StairwayUp. PathPassage and StairwayUp both inherit from the TravelConnector class (along with the Thing class), and that's why we're seeing a similarity. You can read all about TravelConnectors in the section on Travel Connectors and Barriers in the adv3Lite manual, if you like, but you can glean most of what you need to know about them from these two examples (the tree and the path). The canTravelerPass(traveler) method determines whether traveler is allowed to pass through this TravelConnector; in this case we allow it if the traveler (which in this game can only be the player character) is directly or indirectly carrying the gold skull (he'd be carrying it indirectly if he put the skull in the knapsack and then carried the knapsack, but using isIn(traveler) allows for this possibility too). If we disallow travel, the explainTravelBarrier(travel) method is used to explain why. Finally, the travelDesc() method can be used to define the side-effects of travel, in this case displaying a message and then ending the game in victory.
Can you notice anything that seems to be missing from this new version of the pathBack object? You may remember that on the tree object in the Heidi game we defined destination = topOfTree to control where the player character went when the tree was climbed. Ordinarily we'd do the same here, but since the game is ended before the player character can actually end up anywhere else, there's no point, so we can leave the destination of pathBack as nil. If we actually wanted to continue the game instead of ending it when the player retrieves the gold skull we'd have defined something like destination = camp on the pathBack object.
It so happens that whereas the earlier scheme of defining a method on startroom.southeast resembles the TADS 2 way of doing things, this second scheme of pointing the southeast property to a PathPassage object and then defining various methods of the PathPassage closely resembles the adv3 way. The first way is often more convenient, and that's why adv3Lite allows it, but the second way is more robust when you need something like a PathPassage or a StairwayUp to represent a physical connection between locations. The more abstract TravelConnector class, when not used to represent a physical object, can also be useful in controlling conditional travel (through its canTravelerPass() and explainTravelBarrier() methods) or carrying out the side-effects of travel (through its traveLDesc method). We shall meet more examples of this in due course.