#charset "us-ascii" /* * Copyright (c) 1999, 2002 by Michael J. Roberts. Permission is granted * to anyone to copy and use this file for any purpose. * * This is a starter TADS 3 source file. This is a complete TADS game * that you can compile and run. * * To compile this game in TADS Workbench, open the "Build" menu and * select "Compile for Debugging." To run the game, after compiling it, * open the "Debug" menu and select "Go." * * This is the "advanced" starter game - it has only the minimum set of * definitions needed for a working game. If you would like some more * examples, create a new game, and choose the "introductory" version when * asked for the type of starter game to create. */ /* * Include the main header for the standard TADS 3 adventure library. Note * that this does NOT include the entire source code for the library; this * merely includes some definitions for our use here. The main library * must be "linked" into the finished program by including the file * "adv3.tl" in the list of modules specified when compiling. In TADS * Workbench, simply include adv3.tl in the "Source Files" section of the * project. * * Also include the US English definitions, since this game is written in * English. */ #include #include /* * Our game credits and version information. This object isn't required * by the system, but our GameInfo initialization above needs this for * some of its information. * * IMPORTANT - You should customize some of the text below, as marked: the * name of your game, your byline, and so on. */ versionInfo: GameID IFID = '573a8b18-1008-ca66-9580-9a156f82eefa ' name = 'The Further Adventures of Heidi' byline = 'by An Author' htmlByline = 'by ERIC EVE' version = '3.0.16' authorEmail = 'ERIC EVE ' desc = 'This is an unexciting tutorial game based loosely on The Adventures of Heidi by Roger Firth and Sonja Kesserich.' htmlDesc = 'This is an unexciting tutorial game based loosely on The Adventures of Heidi by Roger Firth and Sonja Kesserich.' showCredit() { /* show our credits */ "The TADS 3 language and library were created by Michael J. Roberts.<.p> The original Adventures of Heidi was a simple tutorial game for the Inform language written by Roger Firth and Sonja Kesserich."; /* * The game credits are displayed first, but the library will * display additional credits for library modules. It's a good * idea to show a blank line after the game credits to separate * them visually from the (usually one-liner) library credits that * follow. */ "\b"; } showAbout() { "The Further Adventures of Heidi<.p> A Tutorial Game for TADS 3"; } ; /* * Define the player character. The name of this object is not important, * but note that it has to match up with the name we use in the main() * routine to initialize the game, below. * * Note that we aren't required to define any vocabulary or description * for this object, because the class Actor, defined in the library, * automatically provides the appropriate definitions for an Actor when * the Actor is serving as the player character. Note also that we don't * have to do anything special in this object definition to make the Actor * the player character; any Actor can serve as the player character, and * we'll establish this one as the PC in main(), below. */ me: Actor /* the initial location */ location = outsideCottage ; + Wearable 'plain pretty blue dress' 'blue dress' "It's quite plain, but you think it pretty enough. " wornBy = me dobjFor(Doff) { check() { failCheck('You can\'t wander around half naked! '); } } ; /* * The only thing that HAS to be done in gameMain is to set the * initialPlayerChar, which can be called anything we like, but which is * normally (since it's the default) called me. * * In practice you'll normally want to override the showIntro() method as * well to display an introduction -- usually one that's rather more * substantial than than shown here. */ gameMain: GameMainDef initialPlayerChar = me showIntro() { "Welcome to the Further Adventures of Heidi!\b"; } showGoodbye() { "<.p>Thanks for playing!\b"; } maxScore = 7 ; /* * This exemplifies how a function may be defined in TADS 3. This one is * fairly pointless, since it's only called from one point in the game. In * a game that called finishGameMsg from may different places, a wrapper * function like this might be more useful. * * Note that the function keyword here is optional, and most TADS 3 code * would omit it, defining the function thus: * * endGame(flag) { finishGameMsg(flag, * [finishOptionUndo,finishOptionFullScore]); } */ function endGame(flag) { finishGameMsg(flag, [finishOptionUndo,finishOptionFullScore]); } /* Modifications to behaviour of various classes */ modify Actor makeProper { if(isProperName == nil && properName != nil) { isProperName = true; name = properName; /* Method 1: */ local tokList = Tokenizer.tokenize(properName); for (local i = 1, local cnt = tokList.length() ; i <= cnt ; ++i) { if(i < cnt) cmdDict.addWord(self, getTokVal(tokList[i]), &adjective); cmdDict.addWord(self, getTokVal(tokList[i]), &noun); } /* * Simpler Alternative: this doesn't add forenames as both * nouns and adjectives, but as adjectives only with the final * name as a noun: */ // initializeVocabWith(properName); } } ; /* * Modifications to Thing to implement a listRemoteContents() method, for * looking through the cottage window. */ modify Thing listLocation_ = nil listRemoteContents(otherLocation) { listLocation_ = otherLocation; try { lookAround(gActor, LookListSpecials | LookListPortables); } finally { listLocation_ = nil; } } adjustLookAroundTable(tab, pov, actor) { inherited(tab, pov, actor); if(listLocation_ != nil) { local lst = tab.keysToList(); foreach(local cur in lst) { if(!cur.isIn(listLocation_)) tab.removeElement(cur); } } } ; /* Definition of additional verbs */ DefineTAction(Cross); VerbRule(Cross) ('cross' | 'ford' | 'wade') singleDobj : CrossAction verbPhrase = 'cross/crossing (what)' ; modify Thing dobjFor(Cross) { preCond = [objVisible] verify() {illogical('{The dobj/he} is not something you can cross. ' ); } } ; DefineTAction(Row); VerbRule(Row) 'row' singleDobj : RowAction verbPhrase = 'row/rowing (what)' ; modify Thing dobjFor(Row) { preCond = [touchObj] verify() { illogical('{You/he} can\'t row {that dobj/him}'); } } ; DefineTAction(Ring); VerbRule(Ring) 'ring' singleDobj : RingAction verbPhrase = 'ring/ring (what)' ; modify Thing dobjFor(Ring) { preCond = [touchObj] verify() { illogical('{You/he} can\'t ring {that dobj/him}'); } } ; /* Definitions of additional special classes */ class Diggable : Floor dobjFor(DigWith) { preCond = [objVisible, touchObj] verify() {} check() {} action() { "Digging here reveals nothing of interest. "; } } putDestMessage = &putDestFloor ; class ForestRoom : OutdoorRoom 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 } ; /* * This MultiInstance effectively extends the functionality of the * ForestRoom class by ensuring that we provide a response to EXAMINE * TREES in every room of the ForestRoom class */ MultiInstance instanceObject : Decoration { 'pine tree*trees*pines' 'pine trees' "The forest is full of tall, fast-growing pines, although the occasional oak, beach and sycamore can occasionally be seen among them. " isPlural = true } initialLocationClass = ForestRoom ; modify Thing dobjFor(CleanWith) { verify() {} check() { failCheck(&cannotCleanMsg); } } ; modify InstructionsAction customVerbs = ['ROW THE BOAT', 'CROSS STREAM','RING THE BELL' ] ; modify playerActionMessages notASurfaceMsg = '{You/he} can\'t put anything on {the iobj/him}. ' ; /* * Now for the actual scenario code - the definition of objects that * define the rooms, things and actors in the game. */ /* * ********************** OUTSIDE THE COTTAGE * *************************/ outsideCottage : OutdoorRoom 'In front of a 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. " east = forest in = cottageDoor west asExit(in) northwest = cottageGarden ; + 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 = dobjMsg('You don\'t have time for that right now. ') notASurfaceMsg = iobjMsg('You can\'t reach the roof. ') ; + cottageDoor : LockableWithKey, Door 'door' 'door' "It's a neat little door, painted green to match the window frame. " keyList = [cottageKey] ; + Distant 'forest' 'forest' "The forest is off to the east. " tooDistantMsg = 'It\'s too far away for that. ' ; cottageWindow : SenseConnector, Fixture 'window' 'window' "The cottage window has a freshly painted green frame. The glass has been recently cleaned. " dobjFor(LookThrough) { verify() {} check() {} action() { local otherLocation; connectorMaterial = glass; if(gActor.isIn(outsideCottage)) { otherLocation = insideCottage; "You peer through the window into the neat little room inside the cottage. "; } else { otherLocation = outsideCottage; "Looking out through the window you see a path leading into the forest. "; } gActor.location.listRemoteContents(otherLocation); } } connectorMaterial = adventium locationList = [outsideCottage, insideCottage] ; /* * ********************** DEEP IN THE FOREST * *************************/ forest : ForestRoom 'Deep in the Forest' 'the depths of the forest' "Through the deep foliage you glimpse a building to the west. A track leads to the northeast, and a path leads south." west = outsideCottage northeast = clearing south = outsideCave ; /* * ********************** THE CLEARING * *************************/ clearing : ForestRoom 'Clearing' "A tall sycamore tree stands in the middle of this clearing. One path winds to the southwest, and another to the north." southwest = forest // up : NoTravelMessage { "The lowest bough is just too high for you to reach. "} up : TravelMessage { ->topOfTree "By clinging on to the bough you manage to haul yourself up the tree. " canTravelerPass(traveler) {return traveler.isIn(chair); } explainTravelBarrier(traveler) { "The lowest bough is just out of reach. "; } } north = forestPath ; + tree : Fixture 'tall sycamore tree' 'sycamore tree' "Standing proud in the middle of the clearing, the stout tree looks like it should be easy to climb. A small pile of loose twigs has gathered at its base. " dobjFor(Climb) remapTo(Up) ; ++ bough : OutOfReach, Fixture 'lowest bough' 'lowest bough' "The lowest bough of the tree is just a bit too high up for you to reach from the ground. " canObjReachContents(obj) { if(obj.posture == standing && obj.location == chair) return true; return inherited(obj); } cannotReachFromOutsideMsg(dest) { return 'The bough is just too far from the ground for you to reach. '; } ; + Decoration 'loose small pile/twigs' 'pile of twigs' "There are several small twigs here, most of them small, insubstantial, and frankly of no use to anyone bigger than a blue-tit <>. <>" dobjFor(Search) asDobjFor(Examine) ; + stick : Hidden 'long substantial stick' 'long stick' "It's about two feet long, a quarter of an inch in diameter, and reasonably straight. " iobjFor(MoveWith) { verify() {} check() {} action() { if(gDobj==nest && !nest.moved) replaceAction(Take, nest); } } ; /* * ********************** THE TOP OF THE TREE * *************************/ topOfTree : FloorlessRoom 'At the top of the tree' 'the top of the tree' "You cling precariously to the trunk, next to a firm, narrow branch. " down = clearing enteringRoom(traveler) { if(!traveler.hasSeen(self) && traveler == gPlayerChar) addToScore(1, 'reaching the top of the tree. '); } bottomRoom = clearing // receiveDrop(obj, desc) // { // obj.moveInto(clearing); // "\^<> falls to the ground below. "; // } roomBeforeAction() { if(gActionIs(Jump)) failCheck('Not here - you might fall to the ground and hurt yourself. '); } roomAfterAction() { if(gActionIs(Yell)) "Your shout is lost on the breeze. "; } ; + branch : Surface, Fixture 'branch' 'branch' "The branch looks too narrow to walk, crawl or climb along, but firm enough to support a reasonable weight. " ; ++ nest : Container 'bird\'s nest' 'bird\'s nest' "It's carefully woven from twigs and moss. " dobjFor(LookIn) { check() { if(!isHeldBy(gActor)) { "You really need to hold the nest to take a good look at what's inside. "; exit; } } action() { if(ring.moved) { "You find nothing else of interest in the nest. "; exit; } ring.makePresent(); "A closer examination of the nest reveals a diamond ring inside! "; addToScore(1, 'finding the ring'); } } dobjFor(Take) { check() { if(!moved && !stick.isIn(gActor)) { "The nest is too far away for you to reach. "; exit; } } action() { if(!moved) "Using the stick you manage to pull the nest near enough to take, which you promptly do. "; inherited; } } dobjFor(MoveWith) { verify() { if(isHeldBy(gActor)) illogicalAlready('{You/he} {is} already holding it. '); } check() { if(gIobj != stick) { "{You/he} can't move the nest with that. "; exit; } } } ; +++ ring : PresentLater, Thing 'platinum diamond ring' 'diamond ring' "The ring comprises a sparkling solitaire diamond set in platinum. It looks like it could be intended as an engagement ring. " ; /* * ********************** INSIDE THE COTTAGE * *************************/ insideCottage : Room 'Inside Cottage' 'the inside of the cottage' "The front parlour of the little cottage looks impeccably neat. The door out is to the east. " out = cottageDoorInside east asExit(out) inRoomName(pov) { return 'inside the cottage'; } remoteRoomContentsLister(other) { return new CustomRoomLister('Through the window, {you/he} see{s}', ' lying on the ground.'); } ; + cottageDoorInside : Lockable, Door -> cottageDoor 'door' 'door'; + chair : Chair 'wooden chair' 'wooden chair' "It's a plain wooden chair. " initSpecialDesc = "A plain wooden chair sits in the corner. " remoteInitSpecialDesc(actor) { "Through the cottage window you can see a plain wooden chair sitting in the corner of the front room. "; } ; /* * ********************** THE FOREST PATH * *************************/ forestPath : ForestRoom 'forest Path' "This broad path leads more or less straight north-south through the forest. To the north the occasional puff of smoke floats up above the trees. " south = clearing north = fireClearing ; /* * ********************** THE FIRE CLEARING * *************************/ fireClearing : OutdoorRoom 'Clearing with Fire' 'the clearing with the fire' "The main feature of this large clearing a large, smouldering charcoal fire that periodically lets off clouds of smoke. A path leads off to the south, and another to the northwest. " south = forestPath northwest = pathByStream ; + fire : Fixture 'large smoking charcoal fire' 'fire' "The fire is burning slowly, turning wood into charcoal. It nevertheless feels quite hot even from a distance, and every now and again lets out billows of smoke that get blown in your direction. " dobjFor(Examine) { verify() {} action() { inherited; } } dobjFor(Smell) remapTo(Smell, smoke) dobjFor(Default) { verify() { illogical('The fire is best left well alone; it\'s very hot and {you/he} do{es}n\'t want to get too close.<.p>'); } } ; + smoke : Vaporous 'smoke' 'smoke' "The thick, grey smoke rises steadily from the fire, but gusts of wind occasionally send billows of it in your direction. " // smellDesc = "The smoke from the fire smells acrid and makes you cough. " ; ++ Odor 'acrid smokey smell/whiff/pong' 'smell of smoke' 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] ; spade : Thing 'sturdy spade' 'spade' @burner "It's a sturdy spade with a broad steel blade and a firm wooden handle. " iobjFor(DigWith) { verify() {} check() {} } ; /* * ********************** BANK OF THE STREAM * *************************/ pathByStream : OutdoorRoom 'By a stream' 'the bank of the stream' "The path through the trees from the southeast comes to an end on the banks of a stream. Across the stream to the west you can see an open meadow. " southeast = fireClearing west = streamWade ; streamWade : RoomConnector room1 = pathByStream room2 = meadow canTravelerPass(traveler) { return boots.isWornBy(traveler); } explainTravelBarrier(traveler) { "Your shoes aren't waterproof. If you wade across you'll get your feet wet and probably catch your death of cold. "; } ; /* * ********************** THE MEADOW * *************************/ meadow : OutdoorRoom 'Large Meadow' "This large, open meadow stretches almost as far as you can see to north, west, and south, but is bordered by a fast-flowing stream to the east. " east = streamWade ; + cottageKey : Key 'small brass brassy key/object/something' 'object' "It's a small brass key, with a faded tag you can no longer read. " initSpecialDesc = "A small brass object lies in the grass. " remoteInitSpecialDesc(actor) { "There is a momentary glint of something brassy as the sun reflects off something lying in the meadow across the stream. "; } dobjFor(Take) { action() { if(!moved) addToScore(1, 'retrieving the key'); inherited; name = 'small brass key'; } } ; DistanceConnector [pathByStream, meadow]; stream : MultiLoc, Fixture 'stream' 'stream' "The stream is not terribly deep at this point, though it's flowing quite rapidly towards the south. " locationList = [pathByStream, meadow] dobjFor(Cross) { verify() {} check() {} action() { replaceAction(TravelVia, streamWade); } } ; /* * ********************** OUTSIDE THE CAVE * *************************/ outsideCave : OutdoorRoom 'Just Outside a Cave' 'just outside the cave' "The path through the trees from the north comes to an end just outside the mouth of a large cave to the south. Behind the cave rises a sheer wall of rock. " north = forest in = insideCave south asExit(in) ; + Enterable 'cave/entrance' 'cave' "The entrance to the cave looks quite narrow, but probably just wide enough for someone to squeeze through. " connector = insideCave ; /* * ********************** INSIDE THE COTTAGE * *************************/ insideCave : DarkRoom 'Inside a large cave' 'the interior of the cave' "The cave is larger than its narrow entrance might lead one to expect. Even a tall adult could stand here quite comfortably, and the cave stretches back quite some way. <>" out = outsideCave north asExit(out) roomParts = [caveFloor, defaultCeiling, caveNorthWall, defaultSouthWall, caveEastWall, defaultWestWall] ; caveFloor : Diggable 'cave floor/ground' 'cave floor' "The floor of the cave is quite sandy; near the centre is <>. " hasBeenDug = nil dobjFor(DigWith) { check() { if(hasBeenDug) { "You've already dug a hole here. "; exit; } } action() { hasBeenDug = true; "You dig a small hole in the sandy floor and find a buried pair of old Wellington boots. "; hole.moveInto(self); addToScore(1, 'finding the boots'); } } ; hole : Container, Fixture 'hole' 'hole' "There's a small round hole, freshly dug in the floor near the centre of the cave. " ; + boots : Wearable 'old wellington pair/boots/wellies' 'old pair of boots' "They look ancient, battered, and scuffed, but probably still waterproof. " initSpecialDesc = "A pair of old Wellington boots lies in the hole. " ; caveNorthWall : DefaultWall 'north wall' 'north wall' "In the north wall is a narrow gap leading out of the cave. " ; caveEastWall : DefaultWall 'east wall' 'east wall' "The east wall of the cave is quite smooth and has the faint remains of something drawn or written on it. Unfortunately it's no longer possible to discern whether it was once a Neolithic cave painting or an example of modern graffiti. " ; /* * ********************** THE COTTAGE GARDEN * *************************/ cottageGarden : OutdoorRoom 'Cottage Garden' "This neat little garden is situated on the north side of the cottage. A stream runs along the bottom of the garden, while a short path disappears through a gap in the fence to the southeast, and another leads westwards down to the road. Next to the fence stands a small garden shed. " southeast = outsideCottage north : NoTravelMessage {"<> "} east : NoTravelMessage {"You can't walk through the fence. "} west : FakeConnector {"That path leads down to the road, and you don't fancy going near all those nasty, smelly, noisy cars right now. " } in = insideShed vocabWords = '(cottage) garden' ; + Decoration 'cottage' 'cottage' "The neat little cottage is situated at the southern end of the garden, which it overlooks through a pair of small windows. "; ; + Decoration 'wooden fence' 'wooden fence' "The tall wooden fence runs along the eastern side of the garden, with a small gap at its southern end. " ; + gardenStream: Fixture 'stream' 'stream' "<>" dobjFor(Cross) { verify() {} check() { failCheck(cannotCrossMsg); } } cannotCrossMsg = ' The stream is quite wide at this point, and too deep to cross. ' ; + Openable, Enterable -> insideShed 'small wooden (garden) shed' 'garden shed' "It's a small, wooden shed. " dobjFor(LookIn) asDobjFor(Enter) /* * The following commented out code shows how matchNameCommon() could * be used here, but in practice it's easier in this case just to use * 'weak' tokens (i.e. tokens enclosed in parentheses) in our * vocabWords property. */ // matchNameCommon(origTokens, adjustedTokens) // { // if(adjustedTokens.indexOf('shed')) // return self; // else // return cottageGarden; // } ; /* * ********************** INSIDE THE SHED * *************************/ insideShed : Room 'Inside the Garden Shed' "The inside of the shed is full of garden implements, leaving just about enough room for one person to stand. An old cupboard stands in the corner. " out = cottageGarden ; + Decoration 'garden implements/hoe/rake/shears' 'garden implements' "There's a hoe, a rake, some shears, and several other bits and pieces. " isPlural = true ; + oars : Thing 'pair/oars' 'pair of oars' "The oars look like they're meant for a small rowing-boat. " bulk = 10 initSpecialDesc = "A pair of oars leans against the wall. " ; + cupboard: ComplexContainer, Heavy 'battered old wooden cupboard' 'old cupboard' "The cupboard is a battered old wooden thing, with chipped blue and white paint. " subContainer : ComplexComponent, OpenableContainer { } subSurface : ComplexComponent, Surface { } ; ++ tin : OpenableContainer 'small square tin' 'small tin' "It's a small square tin with a lid. " subLocation = &subSurface bulkCapacity = 5 ; class Coin : Thing 'pound coin/pound*coins*pounds' 'pound coin' "It's gold in colour, has the Queen's head on one side and One Pound written on the reverse. The edge is inscribed with the words DECUS ET TUTAMEN" isEquivalent = true ; +++ Coin; +++ Coin; +++ Coin; +++ Coin; ++ torch : Flashlight, OpenableContainer 'small blue torch/flashlight' 'small blue torch' "It's just a small blue torch. " subLocation = &subContainer bulkCapacity = 1 dobjFor(TurnOn) { check() { if(! battery.isIn(self)) { "Nothing happens. "; exit; } } } iobjFor(PutIn) { check() { if(gDobj != battery) { "{The dobj/he} doesn't fit in the torch. "; exit; } } action() { inherited; makeOpen(nil); achieve.addToScoreOnce(1); } } notifyRemove(obj) { if(isOn) { "Removing the battery causes the torch to go out. "; makeOn(nil); } } achieve: Achievement { desc = "fitting the battery into the torch" } ; /* * ********************** THE ROWING BOAT * *************************/ boat : Heavy, Enterable -> insideBoat 'rowing boat/dinghy' 'rowing boat' //boat : Heavy 'rowing boat' 'rowing boat' @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] ; boatBottom : Floor 'floor/bottom/(boat)' 'bottom of the boat' ; insideBoat : OutdoorRoom name = ('In the boat (by '+ boat.location.name + ')') desc = "The boat is a plain wooden rowing dinghy with a single wooden seat. It is floating on the stream just by the <>. " out = (boat.location) roomParts = [boatBottom, defaultSky] ; + rowBoat: Fixture 'plain wooden rowing boat/dighy' 'boat' "<>" dobjFor(Take) { verify() {illogical('{You/he} can\'t take the boat - {you\'re/he\'s} in it!'); } } dobjFor(Row) { verify() {} check() { if(!oars.isHeldBy(gActor)) { "{You/he} need to be holding the oars before you can row this boat. "; exit; } } action() { "You row the boat along the stream and under a low bridge, finally arriving at "; if(boat.isIn(jetty)) { "the bottom of the cottage garden.<.p> "; boat.moveInto(cottageGarden); } else { "the side of a small wooden jetty.<.p> "; boat.moveInto(jetty); } nestedAction(Look); } } getFacets = [boat] ; + Chair, Fixture 'single wooden seat' 'wooden seat' ; /* * ********************** THE JETTY * *************************/ jetty : OutdoorRoom 'Jetty' "This small wooden jetty stands on the bank of the stream. Upstream to the east you can see a road-bridge, and a path runs downstream along the bank to the west. Just to the south stands a small shop. " west : FakeConnector {"You could go wandering down the path but you don't feel you have much reason to. "} east : NoTravelMessage {"The path doesn't run under the bridge. "} south = insideShop is asExit(south) ; + Distant 'bridge' 'bridge' "It's a small brick-built hump-backed bridge carrying the road over the stream. " ; + Fixture 'stream' 'stream' "The stream becomes quite wide at this point, almost reaching the proportions of a small river. To the east it flows under a bridge, and to the west it carries on through the village. " dobjFor(Cross) { verify() {} check() { failCheck ('The stream is far too wide and deep to cross here. '); } } ; + Enterable -> insideShop 'small shop/store' 'shop' "The small, timber-clad shop has an open door, above which is a sign reading GENERAL STORE" ; /* * ********************** INSIDE THE SHOP * *************************/ insideShop : Room 'Inside Shop' 'inside the shop' "The interior of the shop is lined with shelves containing all sorts of items, including basic foodstuffs, sweets, snacks, stationery, batteries, soft drinks and tissues. Behind the counter is a door marked 'PRIVATE'. " out = jetty north asExit(out) south : OneWayRoomConnector { destination = backRoom canTravelerPass(traveler) { return traveler != gPlayerChar; } explainTravelBarrier(traveler) { "The counter bars your way to the door. "; } } ; + Decoration 'private door*doors' 'door' "The door marked 'PRIVATE' is on the far side of the counter, and there seems to be no way you can reach it. The other door out to the jetty is to the north. " ; + Fixture, Surface 'counter' 'counter' "The counter is about four feet long and eighteen inches wide. " ; ++ bell : Thing 'small brass bell' 'small brass bell' "The bell comprises an inverted hemisphere with a small brass knob protruding through the top. Attached to the bell is a small sign. " dobjFor(Ring) { verify() {} check() {} action() { bellRing.triggerEvent(self); } } ; +++ Component, Readable 'sign' 'sign' "The sign reads RING BELL FOR SERVICE. " ; +++ Component 'knob/button' 'knob' "The knob protrudes through the top of the brass hemisphere of the bell. " dobjFor(Push) remapTo(Ring, bell) ; + Distant, Surface 'shelf*shelves' 'shelves' "The shelves with the most interesting goodies are behind the counter. " isPlural = true ; ++ batteries : Distant 'battery*batteries' 'batteries on shelf' "A variety of batteries sits on the shelf behind the counter. " isPlural = true salePrice = 3 saleName = 'torch battery' saleItem = battery ; ++ sweets : Distant 'candy/sweets' 'sweets on shelf' "All sorts of tempting jars, bags, packets and boxes of sweets lurk temptingly on the shelves behind the counter. " isPlural = true salePrice = 1 saleName = 'bag of sweets' saleItem = sweetBag ; battery : Thing 'small red battery' 'small red battery' "It's a small red battery, 1.5v, manufactured by ElectroLeax and made in the People's Republic of Erewhon. " bulk = 1 ; sweetBag : Dispenser 'bag of candy/sweets' 'bag of sweets' "A bag of sweets. " canReturnItem = true myItemClass = Sweet ; class Sweet : Dispensable, Food desc = "It's a small, round, clear, <> boiled sweet. " vocabWords = 'sweet/candy*sweets' location = sweetBag listWith = [sweetGroup] sweetGroupBaseName = '' collectiveGroup = sweetCollective sweetGroupName = ('one ' + sweetGroupBaseName) countedSweetGroupName(cnt) { return spellIntBelow(cnt, 100) + ' ' + sweetGroupBaseName; } tasteDesc = "It tastes sweet and tangy. " dobjFor(Eat) { action() { "You pop <> into your mouth and suck it. It tastes nice but it doesn't last as long as you'd like.<.p>"; inherited; } } ; class RedSweet : Sweet 'red - ' 'red sweet' isEquivalent = true sweetGroupBaseName = 'red' ; class GreenSweet : Sweet 'green - ' 'green sweet' isEquivalent = true sweetGroupBaseName = 'green' ; class YellowSweet : Sweet 'yellow - ' 'yellow sweet' isEquivalent = true sweetGroupBaseName = 'yellow' ; sweetGroup: ListGroupParen showGroupCountName(lst) { "<> sweets"; } showGroupItem(lister, obj, options, pov, info) { say(obj.sweetGroupName); } showGroupItemCounted(lister, lst, options, pov, infoTab) { say(lst[1].countedSweetGroupName(lst.length())); } ; sweetCollective: ItemizingCollectiveGroup 'candy*sweets' 'sweets' ; RedSweet; RedSweet; RedSweet; RedSweet; GreenSweet; GreenSweet; GreenSweet; YellowSweet; YellowSweet; bellRing : SoundEvent triggerEvent(source) { "TING!<.p>"; inherited(source); } ; backRoom: Room north = insideShop ; SenseConnector, Intangible 'wall' 'wall' connectorMaterial = paper locationList = [backRoom, insideShop] ; /* * ********************************************************************* * NON PLAYER CHARACTERS * ************************************************************************/ /* * Definition of Joe Black, the Burner NPC - as this is complex we * separate it from the other code and put it at the end */ burner : Person 'charcoal burner' 'charcoal burner' @fireClearing "It's rather difficult to make out his features under all the grime and soot. " properName = 'Joe Black' globalParamName = 'burner' isHim = true ; + GiveShowTopic @ring topicResponse { "As you hand the ring over to {the burner/him}, his eyes light up in delight and his jaws drop in amazement. You found it! he declares, God bless you, you really found it! Now I can go and call on my sweetheart after all! Thank you, my dear, that's absolutely wonderful!<.p>"; addToScore (2, 'giving {the burner/him} his ring back. '); /* CHANGED IN 3.0.6n */ finishGameMsg(ftVictory, [finishOptionUndo,finishOptionFullScore]); } ; + DefaultGiveShowTopic, ShuffledEventList [ '{The burner/he} shakes his head, No thanks, love.', 'He looks at it and grins, That\'s nice, my dear. he remarks, handing it back.', 'I\'d hang on to that if I were you. he advises. ' ] ; + burnerTalking : InConversationState stateDesc = "He's standing talking with you. " specialDesc = "{The burner/he} is leaning on his spade talking with you. " nextState = burnerWorking ; ++ burnerWorking : ConversationReadyState stateDesc = "He's busily tending the fire. " specialDesc = "<> is walking round the fire, occasionally shovelling dirt onto it with his spade. " isInitState = true a = 0 ; +++ HelloTopic, StopEventList [ 'Er, excuse me, you say, trying to get {the\'s burner/her} attention.\b {The burner/he} moves away from the fire and leans on his spade to talk to you. Hello, young lady. Mind you don\'t get too close to that fire now.', 'Hello! you call cheerfully.\b Hello again! {the burner/he} declares, pausing from his labours to rest on his spade.' ] ; +++ ByeTopic "Bye for now, then. you say.<.p> Take care, now. {the burner/he} admonishes you as he returns to his work." ; +++ ImpByeTopic "{The burner/he} gives a little shake of the head and returns to work." ; ++ AskTopic, SuggestedAskTopic @smoke "Doesn't that smoke bother you? you ask.<.p> Nah! You get used to it - you just learn not to breathe too deep when it gets blown your way. he assures you." name = 'the smoke' ; ++ AskTopic, SuggestedAskTopic, ShuffledEventList @fire [ 'Why have you got that great big bonfire in the middle of the forest? you ask.<.p> It\'s not a bonfire, Miss, it\'s a fire for making charcoal. he explains, And to make charcoal I need to burn wood - slow like - and a forest is a good place to find wood - see?', 'Doesn\'t it get a bit hot, working with that fire all day? you wonder.<.p> Yes, but it beats being cooped up in an office all day. he replies, I couldn\'t stand that!', 'Why do you keep putting that dust on the fire? you wonder.<.p> To stop it burning too quick. he tells you.' ] name = 'the fire' ; ++ AskTopic, SuggestedAskTopic @burner "My name's Heidi. you announce. What's yours?<.p> <>, he replies, Mind you, it'll soon be mud. <.convnode burner-mud><>" name = 'himself' ; +++ AltTopic, SuggestedAskTopic, StopEventList [ 'Have you been a charcoal burner long? you ask.<.p> About ten years. he replies.', 'Do you like being a charcoal burner? you wonder, It seems rather messy!<.p> It\'s better than being cooped up in some office or factory all day, at any rate. he tells you.', 'What do you do when you\'re not burning charcoal? you enquire.<.p> Oh -- this and that. he shrugs.' ] isActive = (burner.isProperName) name = 'himself' ; ++ AskTellTopic, SuggestedAskTopic, StopEventList @ring [ 'What happened to the ring -- how did you manage to lose it? you ask.<.p> You wouldn\'t believe it. he shakes his head again, I took it out to take a look at it a couple of hours back, and then dropped the thing. Before I could pick it up again, blow me if a thieving little magpie didn\'t flutter down and fly off with it!', 'Where do you think the ring could have gone? you wonder.<.p> I suppose it\'s fetched up in some nest somewhere, he sighs, Goodness knows how I\'ll ever get it back again!', 'Would you like me to try to find your ring for you? you volunteer earnestly.<.p> Yes, sure, that would be wonderful. he agrees, without sounding in the least convinced that you\'ll be able to.' ] name = 'the ring' ; +++ AltTopic, SuggestedTellTopic "I found a ring in a bird's nest, up a tree just down there. you tell him, pointing vaguely southwards, Could it be yours?<.p> Really? he asks, his eyes lighting up with disbelieving hope, Let me see it!" isActive = (gPlayerChar.hasSeen(ring)) name = 'the ring' ; ++ AskForTopic @spade topicResponse { "Could I borrow your spade, please? you ask.<.p> All right then, he agrees a little reluctantly, handing you the spade, but make sure you bring it back."; spade.moveInto(gActor); getActor().setCurState(burnerFretting); } ; ++ DefaultAskTellTopic "What do you think about <>? you ask.<.p> Ah, yes indeed, <>, he nods sagely, <>." ; + ConvNode 'burner-mud'; ++ SpecialTopic, StopEventList 'deny that mud is a name' ['deny', 'that', 'mud', 'is', 'a', 'name'] [ 'Mud! What kind of name is that? you ask.<.p> My name -- tonight. he replied gloomily.<.convstay>', 'But you can\'t really be called Mud you insist.<.p> Oh yes I can! he assures you.<.convstay>' ] ; ++ SpecialTopic 'ask why' ['ask','why'] "Why will your name be mud? you want to know.<.p> He shakes his head, lets out an enormous sigh, and replies, I was going to give her the ring tonight -- her engagement ring -- only I've gone and lost it. Cost me two months' wages it did. And now she'll never speak to me again, he concludes, with another mournful shake of the head, never.<>" ; ++ DefaultAskTellTopic "And why does... you begin.<.p> Mud. he repeats with a despairing sigh.<.convstay>" ; + burnerFretting : InConversationState specialDesc = "{The burner/he} is standing talking to you with his hands on his hips. " stateDesc = "He's standing talking to you with his hands on his hips. " nextState = burnerWaiting ; ++ burnerWaiting : ConversationReadyState specialDesc = "{The burner/he} is walking round the fire, frowning as he keeps instinctively reaching for the spade that isn't there. " stateDesc = "He's walking round the fire. " afterTravel(traveler, connector) { getActor().initiateConversation(burnerFretting, 'burner-spade'); } ; +++ HelloTopic "Hello, there. you say.<.p> Hello, young lady - have you brought my spade? he asks." ; +++ ByeTopic "Bye, then. you say.<.p> Don't be too long with that spade -- be sure to bring it right back! he admonishes you." ; ++ GiveShowTopic @spade topicResponse { "Here's your spade then, you say, handing it over.<.p> Ah, thanks! he replies taking it with evident relief. "; spade.moveInto(burner); burner.setCurState(burnerTalking); } ; ++ AskForTopic @spade "He doesn't have the spade. " isConversational = nil ; ++ AskTellTopic @spade "This seems a very sturdy spade, you remark.\b It is -- look after it well, I need it for my work! {the burner/he} replies." ; +++ AltTopic "I seem to have left your spade somewhere, you confess.\b I hope you can find it then! {the burner/he} remarks anxiously." isActive = (!spade.isIn(burner.location)) ; ++ DefaultAskTellTopic "We can talk about that when I've got my spade back, he tells you." ; + ConvNode 'burner-spade' npcGreetingMsg = "<.p>He looks up at your approach, and walks away from the fire to meet you. Have you finished with my spade yet? he enquires anxiously.<.p>" npcContinueMsg = "What about my spade? Have you finished with it yet? {the burner/he} repeats anxiously. " ; ++ YesTopic "Yes, I have. you reply.<.p> Can I have it back then, please? he asks." ; ++ NoTopic "Not quite; can I borrow it a bit longer? you ask.<.p> Very well, then. he conceded grudgingly, But I need it to get on with my job, so please be quick about it." ; /* * Definition of Sally the Shopkeeper */ shopkeeper : SoundObserver, Person 'young shopkeeper/woman' 'young shopkeeper' @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) "All right, all right, here I am! 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'); } globalParamName = 'shopkeeper' cashReceived = 0 price = 0 saleObject = nil // cashFuseID = nil // cashFuse // { // if(saleObject == nil) // { // "What's this for? asks {the shopkeeper/she}, handing the money // back, Shouldn't you tell me what you want to buy first?"; // cashReceived = 0; // } // else if(cashReceived < price) // "Er, that's not enough. she points out, looking at you expectantly // while she waits for the balance. "; // else // { // "{The shopkeeper/she} takes the money and turns to take <> // off the shelf. She hands you <> saying, Here you are // then"; // if(cashReceived > price) // ", and here's your change"; // ".

"; // saleObject.moveInto(gPlayerChar); // price = 0; // cashReceived = 0; // saleObject = nil; // } // cashFuseID = nil; // } ; + ConvNode 'sally-1' npcGreetingMsg = "Hello, what can I get you? she asks. <.p>" ; + sallyTalking : InConversationState specialDesc = "{The shopkeeper/she} is standing behind the counter talking with you. " stateDesc = "She's standing behind the counter talking with you. " nextState = sallyWaiting ; ++ sallyWaiting : ConversationReadyState specialDesc = "{The shopkeeper/she} is standing behind the counter, checking the stock on the shelves. " stateDesc = "She's checking the stock on the shelves behind the counter. " isInitState = true takeTurn { if(!gPlayerChar.isIn(insideShop) && shopkeeper.isIn(insideShop)) shopkeeper.moveIntoForTravel(backRoom); inherited; } ; +++ HelloTopic "Hello, <>, you say.<.p> Hello, <>, what can I do for you? asks {the shopkeeper/she}." ; +++ ByeTopic "'Bye, then! you say.<.p> Goodbye<>. See you again soon! {the shopkeeper/she} beams in return." ; +++ ImpByeTopic "{The shopkeeper/she} turns away and starts checking the stock on the shelves." ; ++ AskTellTopic [shopkeeper, gPlayerChar] "I'm Heidi. What's your name? you ask.<.p> Hello, Heidi; I'm <>, she smiles. <>" ; +++ AltTopic "I'm feeling really very well today; how are you? you ask.<.p> I'm feeling very well too, thanks. she tells you." isActive = (shopkeeper.isProperName) ; ++ AskTellTopic @burner "Do you know {the burner/him}, the old fellow who works in the forest? you enquire innocently.<.p> He's not that old, she replies coyly." ; ++ AskTellTopic @tWeather "Lovely weather we're having, don't you think? you remark.<.p> Absolutely, she agrees, and with luck, it should stay fine tomorrow." ; ++ DefaultAskTellTopic, ShuffledEventList [ 'What do you think about ' + gTopicText + '? you ask.<.p> Frankly, not a lot. she replies.', 'I think it\'s really interesting that... you begin.<.p> Oh yes, really interesting. she agrees.', 'You make polite conversation about ' + gTopicText + ' and {the shopkeeper/she} makes polite conversation in return.' ] ; ++ DefaultGiveShowTopic "No thanks, dear. she says, handing it back. "; ; ++ GiveShowTopic matchTopic(fromActor, obj) { return obj.ofKind(Coin) ? matchScore : 0; } handleTopic(fromActor, obj) { // if(shopkeeper.cashFuseID == nil) // shopkeeper.cashFuseID = new Fuse(shopkeeper, &cashFuse, 0); shopkeeper.cashReceived ++; currency = obj; // if(shopkeeper.cashReceived > 1) // "number <>"; if(shopkeeper.cashReceived <= shopkeeper.price) obj.moveInto(shopkeeper); /* add our special report */ gTranscript.addReport(new GiveCoinReport(obj)); /* register for collective handling at the end of the command */ gAction.callAfterActionMain(self); } afterActionMain() { /* * adjust the transcript by summarizing consecutive coin * acceptance reports */ gTranscript.summarizeAction( {x: x.ofKind(GiveCoinReport)}, {vec: 'You hand over ' + spellInt(vec.length())+' ' + currency.name+'s.\n'}); if(shopkeeper.saleObject == nil) { "What's this for? asks {the shopkeeper/she}, handing the money back, Shouldn't you tell me what you want to buy first?"; shopkeeper.cashReceived = 0; } else if(shopkeeper.cashReceived < shopkeeper.price) "Er, that's not enough. she points out, looking at you expectantly while she waits for the balance. "; else { "{The shopkeeper/she} takes the money and turns to take <> off the shelf. She hands you <> saying, Here you are then"; if(shopkeeper.cashReceived > shopkeeper.price) ", and here's your change"; ".

"; shopkeeper.saleObject.moveInto(gPlayerChar); shopkeeper.price = 0; shopkeeper.cashReceived = 0; shopkeeper.saleObject = nil; } } currency = nil ; class GiveCoinReport: MainCommandReport construct(obj) { /* remember the coin we accepted */ coinObj = obj; /* inherit the default handling */ gMessageParams(obj); inherited('You hand over {a obj/him}. '); } /* my coin object */ coinObj = nil ; ++ BuyTopic @batteries alreadyBought = "You only need one battery, and you've already bought it.<.p>" ; ++ BuyTopic @sweets alreadyBought = "You've already bought one bag of sweets. Think of your figure! Think of your teeth!<.p>" ; class BuyTopic : AskAboutForTopic topicResponse { if(matchObj.saleItem.moved) alreadyBought; else if (shopkeeper.saleObject == matchObj.saleItem) "Can I have the <>, please? you ask.<.p> I need another <> from you. she points out.<.p>"; else if (shopkeeper.saleObject != nil) "Oh, and I'd like a <> too, please. you announce.<.p> Shall we finish dealing with the <> first? {the shopkeeper/she} suggests. "; else { purchaseRequest(); purchaseResponse(); shopkeeper.price = matchObj.salePrice; shopkeeper.saleObject = matchObj.saleItem; } } alreadyBought = "You've already bought a <>.<.p>" purchaseRequest = "I'd like a <> please, you request.<.p>" purchaseResponse = "Certainly, that'll be <>, {the shopkeeper/she} informs you.<.p>" ; function currencyString(amount) { return spellInt(amount) + ' ' + ((amount>1) ? 'pounds' : 'pound'); } tWeather : Topic 'weather'; /* * ******************** DEBUGGING VERBS * ***********************/ #ifdef __DEBUG /* * The purpose of the everything object is to contain a list of all usable * game objects which can be used as a list of objects in scope for * certain debugging verb. Everything caches a list of all relevant * objects the first time its lst method is called. */ everything : object /* * lst_ will contain the list of all objects. We initialize it to nil * to show that the list is yet to be cached */ lst_ = nil /* * The lst_ method checks whether the list of objects has been cached * yet. If so, it simply returns it; if not, it calls initLst to build * it first (and then returns it). */ lst() { if (lst_ == nil) initLst(); return lst_; } /* * initLst loops through every game object descended from Thing and * adds it to lst_, thereby constructing a list of physical game * objects. */ initLst() { lst_ = new Vector(50); local obj = firstObj(); while (obj != nil) { if(obj.ofKind(Thing)) lst_.append(obj); obj = nextObj(obj); } lst_ = lst_.toList(); } ; DefineTAction(Purloin) cacheScopeList() { scope_ = everything.lst(); } ; VerbRule(Purloin) ('purloin'|'pn') dobjList :PurloinAction verbPhrase = 'purloin/purloining (what)' ; modify Thing dobjFor(Purloin) { verify() { if(isHeldBy(gActor)) illogicalNow('{You/he} {is} already holding it. '); } check() {} action { mainReport('{The/he dobj} pops into your hands.\n '); moveInto(gActor); } } ; modify Fixture dobjFor(Purloin) { verify {illogical ('That is not something you can purloin - it is fixed in place.'); } } ; modify Immovable dobjFor(Purloin) { check() { "You can't take {the/him dobj}. "; exit; } } ; DefineTAction(Gonear) // cacheScopeList() // { // scope_ = everything.lst(); // } /* * defining objInScope is an alternative to defining cacheScopeList in * this particular situation; you can try out either way by commenting * out one method and uncommenting in the other. This also applies to * the Purloin verb. */ objInScope(obj) { return true; } ; VerbRule(Gonear) ('gonear'|'gn'|'go' 'near') singleDobj :GonearAction verbPhrase = 'gonear/going near (what)' ; modify Thing dobjFor(Gonear) { verify() {} check() {} action() { local obj = self.roomLocation; if(obj != nil) { "{You/he} {are} miraculously transported...

"; replaceAction(TravelVia, obj); } else "{You/he} can't go there. "; } } ; modify Decoration dobjFor(Gonear) { verify() {} check() {} action() {inherited;} } ; modify Distant dobjFor(Gonear) { verify() {} check() {} action() {inherited;} } ; modify MultiLoc dobjFor(Gonear) { verify() { illogical('{You/he} cannot gonear {the dobj/him}, since it exists in more than one location. '); } } dobjFor(Purloin) { verify() { illogical('{You/he} cannot purloin {the dobj/him}, since it exists in more than one location. '); } } ; #endif