Chapter 8 - Finishing Off
Filling in Some Gaps |
a. | Atmosphere Strings
|
|
For the first example, consider the forest through which Heidi keeps passing. As it stands, the only other living creature she ever encounters there is Joe the charcoal burner; but one would expect a real forest to have all sorts of life in it. It's not worth creating lots of animal objects to represent the living forest, but we can use the atmosphereStrings property to simulate its presence. Rather than coding a separate atmosphereStrings for each room we consider to be part of the forest, it will be quicker and easier to define our own ForestRoom class that encapsulates this behaviour:
|
atmosphereList : ShuffledEventList
{
[
'A fox dashes across your path.\n',
'A clutch of rabbits dash back among the trees.\n',
'A deer suddenly leaps out from the trees, then darts back off into
the forest.\n',
'There is a rustling in the undergrowth.\n',
'There is a sudden flapping of wings as a pair of birds take flight
off to the left.\n'
]
eventPercent = 90
eventReduceAfter = 6
eventReduceTo = 50
}
;
|
The three rooms that might best be redefined as belonging to the ForestRoom class are forest, clearing, and forestPath; you could also add define fireClearing to be of class ForestRoom, but in practice you'll probably find that the atmosphere strings tend to get in the way of the conversation with Joe.
|
|
b. | Sensory Emanations
|
|
|
sourceDesc = "The smoke from the fire smells acrid and makes you cough. "
descWithSource = "The smoke smells strongly of charred wood."
hereWithSource = "You catch a whiff of the smoke from the fire. "
displaySchedule = [2, 4, 6]
;
|
If you want to vary the message displayed according to the schedule, you can override hereWithSource (and/or hereWithoutSource if appropriate) with a method that checks the displayCount property (which is reset to 1 each time after the object comes within sensory range after it has left it (in this case, each time you return to the fire clearing after having been somewhere else). For example:
|
{
switch (displayCount)
{
case 1:
"You catch a whiff of the smoke from the fire. ";
break;
case 2:
"You catch another whiff of smoke from the fire. ";
break;
default:
"You catch yet another whiff of smoke from that wretched fire. ";
}
}
|
Note that you can also define a Noise object in much the same way as the Odor object is defined here (there are a couple of examples in sample.t). Perhaps, for example, the fire is making a crackling sound - once again the implementation can be left as an exercise for the interested reader (begin by defining ++Noise 'crackling sound/noise' 'crackling sound' directly after the fire object, and then follow precisely the same format as used for the Odor object, making the obviously necessary adjustments to refer to sounds instead of smells).
|
|
c. | Settling the Score
|
|
|
maxScore = 7
|
|
|
initialPlayerChar = me
showIntro()
{
"Welcome to the Further Adventures of Heidi!\b";
}
showGoodbye()
{
"<.p>Thanks for playing!\b";
}
maxScore = 7
;
|
Of course, you may think that there should be more opportunities for gaining points. In that case you can add more addToScore calls at the appropriate places, being careful to ensure than they can only be called once (or use the addToScoreOnce method on new Achievement objects), and then adjust gameMain.maxScore accordingly.
|
d. | Destination Names
|
|
|
|
|
destName = 'the front of the cottage'
|
|
|
'the front of the cottage'
"You stand just outside a cottage; the forest stretches east.
A short path leads round the cottage to the northwest. "
|
|
"You cling precariously to the trunk, next to a firm,
narrow branch. "
|
|
|
e. | Stopping Sally's Misbehaviour
|
|
The easiest way to go about it is to stop the daemon code actually executing unless Heidi is in the shop. We can do that by rewriting the shopkeeper's daemon method thus:
|
{
if(gPlayerChar.isIn(insideShop))
{
moveIntoForTravel(insideShop);
"{The shopkeeper/she} comes through the door and
stands behind the counter.<.p>";
daemonID.removeEvent();
daemonID = nil;
initiateConversation(sallyTalking, 'sally-1');
}
}
|
|
|
daemonID = new Daemon(self, &daemon, 2);
|
|
to read:
|
|
daemonID = new SenseDaemon(self, &daemon, 2, self, sight);
|
|
|
|
@backRoom
"The shopkeeper is a jolly woman with rosy cheeks and
fluffy blonde curls. "
isHer = true
properName = 'Sally'
notifySoundEvent(event, source, info)
{
if(event == bellRing && daemonID == nil && isIn(backRoom))
daemonID = new SenseDaemon(self, &daemon, 2, self, sight);
else if(isIn(insideShop) && event == bellRing)
"<q>All right, all right, here I am!</q> says
{the shopkeeper/she}.<.p>";
}
daemonID = nil
daemon
{
moveIntoForTravel(insideShop);
"{The shopkeeper/she} comes through the door and
stands behind the counter.<.p>";
daemonID.removeEvent();
daemonID = nil;
if(canTalkTo(gPlayerChar))
initiateConversation(sallyTalking, 'sally-1');
}
// continue as before
;
|
|
f. | Finishing the Boat
|
|
First of all, if the player enters the command row the boat when Heidi is in the garden, the game will reply with "You can't row that.", which is not entirely true. We need to provide a more appropriate response here. Again this is something you can probably work out how to do yourself by now, so have a go at it before reading on.
The second problem is a bit more subtle. Suppose that after issuing the command row the boat when Heidi is in the garden, the player types enter it followed by row it. The game will now respond with "The word 'it' doesn't refer to anything right now." You can probably work out why: the parser thinks that 'it' refers to the object we used to implement the outside of the boat, but once Heidi's entered the boat, that object is no longer in scope. We can cure this by using the getFacets property. This holds a list of other objects that we, the game author, consider to be facets of the same object, so that once we've referred to any of the objects representing the boat, the pronoun 'it' can refer to any of its facets currently in scope. So, for example, if we give the name rowBoat to the previously anonymous Fixture we placed in insideBoat to act as the target of a row command, we can now define getFacets = [rowBoat] on the boat object, and conversely getFacets = [boat] on the rowBoat object, and having referred to one, we can freely use 'it' to refer to the other.
The definition of the boat then becomes:
|
@cottageGarden
"It's a small rowing boat. "
specialDesc = "A small rowing boat floats on the stream,
just by the bank. "
useSpecialDesc { return true; }
dobjFor(Board) asDobjFor(Enter)
dobjFor(Row)
{
verify()
{
illogicalNow('You need to be aboard the boat before you can row it. ');
}
}
getFacets = [rowBoat]
;
|
|
|
g. | Other Suggestions - including an MultiInstance
|
|
But one new thing of this type does suggest itself, and that is putting some trees in the forest, since allowing the player to experience the following would be less than optimal:
|
Through the deep foliage you glimpse a building to the west. A track leads to the northeast, and a path leads south.
There is a rustling in the undergrowth.
>x trees
The word "trees" is not necessary in this story.
|
|
instanceObject : Decoration { 'pine tree*trees*pines' 'pine trees'
"The forest is full of tall, fast-growing pines, although the
occasional oak,
|
isPlural = true
}
initialLocationClass = ForestRoom
;
|
We could have implemented these trees as a MultiLoc, and in this particular game there would have been no functional difference. Strictly speaking, though, MultiInstance is the more correct class to use here. The main use for a MultiLoc is for a single physical object that exists in more than one location by virtue of being situated at the border of two or more rooms. For example, a large town square with a fountain at its centre might be implemented as four rooms, with the central fountain being a MultiLoc that appears in each. It is physically the same fountain whether it is viewed from, say, the northeast or the southeast corner of the square, and if the Player Character throws a coin into the fountain from the northeast corner of the square, he or she should then be able to retrieve it from the fountain even after moving to another part of the square, since it remains the same physical fountain. A MultiLoc may also be used for a Distant object, such as a far-off mountain range or the moon, that is visible from a number of different locations, since once again it is the same physical object that is being represented (provided it appears identical from all the locations in question).
But in this case, we are not trying to implement the same clump of trees visible from all parts of the forest, but the fact that there are trees, albeit numerically different trees, in all parts of the forest. Since all these trees are functionally identical (apart from the sycamore tree in the clearing that we have implemented separately) we can use MultiInstance as a short-cut to creating them all over the forest. Although in this game it makes no practical difference to the player whether we use a MultiLoc or a MultiInstance, in general it may. If, for example, Heidi were exploring the forest by night, then MultiLoc trees illuminated in one room would appear illuminated in all rooms (since they represent the same physical object). This would mean that if Heidi dropped her torch/flashlight at one spot in the forest and then moved to another part of the forest without any illumination, she'd be in a totally dark room but still be able to examine the trees, which is probably not what we'd want. Using MultiInstance ensures that we do note get this sort of unwanted behaviour.
You might think that a problem here would be that if the player types examine tree while Heidi is in the sycamore tree clearing, the parser will ask, "Which tree do you mean, the pine trees or the tree?". But in fact the library automatically takes care of this by giving a Decoration object a lower 'logical rank' than a normal object; that means that if two objects are in scope which might match the same vocabulary, one of them being a Decoration object and the other not, the other will be chosen in response to an examine command. So in this case when the player types examine tree the parser will assume that it is the sycamore tree that is meant, without troubling the player with a disambiguation request. For a fuller discussion of 'logical rank' see the discussion of verify above (and the section on 'Action Results' in the Technical Manual).
|
|
customVerbs = ['ROW THE BOAT', 'CROSS STREAM', 'RING THE BELL' ]
;
|
Getting Started in TADS 3
[Main]
[Previous] [Next]