#charset "us-ascii" /* * Sample game using the TADS 3 library. * * This is an extremely contrived game, and is not intended to be the * least bit interesting to a player but rather serves as a test of * various library features during library development. */ #include "adv3.h" #include "tok.h" #include "en_us.h" /* * This is how we'd change a library grammar definition for a verb, if * we wanted to replace library grammar with our own. This doesn't * change the rest of the VerbRule definition; it merely changes the * grammar. If we wanted to change the rest of the VerbRule, we'd use * 'replace' rather than 'modify'. Note that 'replace' and' modify' * both replace the grammar rule itself, though; they differ in how they * handle the *rest* of the properties of the VerbRule object. * * To delete a library verb rule grammar, modify the rule and use an * unmatchable token as the replacement grammar rule (' ' will work, * since the standard tokenizer won't return a space character as a * token). */ //modify VerbRule(Examine) // ('ex' | 'lkat') DobjList : //; /* ------------------------------------------------------------------------ */ /* * Game credits and version information */ versionInfo: GameID IFID = 'fa555579-c107-f533-d2a8-d2c93ca87ea0' name = 'TADS 3 Library Sampler' version = '1.0' byline = 'by M.J.Roberts' htmlByline = 'by M. J. Roberts' authorEmail = 'M.J. Roberts ' desc = 'A random sample and test of features of the tads 3 library.' htmlDesc = 'A random sample and test of features of the TADS 3 library.' /* add some special text of our own for the credits */ showCredit() { "<>\n <>\b Thanks to everyone who has participated in the TADS 3 library design process for contributing so many great ideas. \b
* * * \n
\b"; } /* show "about" information */ showAbout() { "This is just a boring sample game to test features of the library under construction. Everything in this game is contrived to exercise a particular set of library features, and we make no attempt to make a coherent game out of this hodge-podge of tests. A more interesting demonstration game will have to wait until the library is further along. "; } ; /* ------------------------------------------------------------------------ */ /* * Set up a barrier object so that we can conveniently prevent the * tricycle from going past certain points. This is just a generic * vehicle barrier, but we customize the failure message. */ tricycleBarrier: VehicleBarrier construct(msg) { msg_ = msg; } explainTravelBarrier(traveler) { if (traveler == tricycle) reportFailure(msg_); else inherited(traveler); } msg_ = '{You/he}\'d best stay indoors with the tricycle; it would probably break if you rode it outside. ' ; /* set up a barrier against pushing the television out of the house */ tvBarrier: PushTravelBarrier construct(msg) { msg_ = msg; } canPushedObjectPass(obj) { return obj != television; } explainTravelBarrier(traveler) { reportFailure(msg_); } msg_ = 'The television\'s casters are too small to allow the TV to go beyond this point. ' ; /* ------------------------------------------------------------------------ */ /* * Trivial class for rooms in the house. We use this mostly for * identification of house rooms. */ class HouseRoom: Room ; class DarkHouseRoom: DarkRoom, HouseRoom ; /* ------------------------------------------------------------------------ */ /* * Basic Coin class */ class Coin: Thing vocabWords = 'coin*coins' listWith = [coinGroup] /* * The base name for the coin as it appears in groups. We leave off * the word "coin" because the coin group prefix uses it. */ coinGroupBaseName = '' /* use the coinCollective for selected actions on coins */ collectiveGroups = [coinCollective] /* * coin group name and counted name - we synthesize these from the * group base name */ coinGroupName = ('one ' + coinGroupBaseName) countedCoinGroupName(cnt) { return spellIntBelow(cnt, 100) + ' ' + coinGroupBaseName; } ; /* * copper coins */ class CopperCoin: Coin 'copper -' 'copper coin' @livingRoom isEquivalent = true coinValue = 1 coinGroupBaseName = 'copper' ; /* * silver coins */ class SilverCoin: Coin 'silver -' 'silver coin' @livingRoom isEquivalent = true coinValue = 5 coinGroupBaseName = 'silver' ; /* * gold coins */ class GoldCoin: Coin 'gold -' 'gold coin' @livingRoom isEquivalent = true coinValue = 10 coinGroupBaseName = 'gold' /* flag: I've awarded points for being taken by the player character */ takeAwarded = nil /* award points when the item is first taken */ afterAction() { /* inherit the default handling */ inherited(); /* * if I'm now being held directly or indirectly, and I haven't * awarded my points for being taken before, award points now */ if (!takeAwarded && isIn(libGlobal.playerChar)) { /* award my points */ goldCoinAchievement.awardPoints(); /* only award this achievement once per coin */ takeAwarded = true; } } ; /* * Coin group - this is a listing group we use to group coins in room * contents lists, object contents lists, and inventory lists. */ coinGroup: ListGroupParen showGroupCountName(lst) { "<> coins"; } /* * Just for the heck of it, order coins in a group in descending * order of monetary value. Since we want descending order, return * the negative of the value comparison. */ compareGroupItems(a, b) { return b.coinValue - a.coinValue; } /* * Customize the display of the individual coins listed in a coin * group, so that we leave off the word "coin" from the name. This * will make our group list look like so: * * five coins (two gold, two silver, one copper) */ showGroupItem(lister, obj, options, pov, info) { say(obj.coinGroupName); } showGroupItemCounted(lister, lst, options, pov, infoTab) { say(lst[1].countedCoinGroupName(lst.length())); } ; /* * Coin Collective - this is a collective group object that we use to * perform certain actions on coins collectively, rather than iteratively * on the individuals. This is an "itemizing" collective group, because * we want "examine coins" to show a message listing the individual * coins. */ coinCollective: ItemizingCollectiveGroup '*coins' 'coins' ; /* ------------------------------------------------------------------------ */ /* * Balloons */ class Balloon: Thing 'inflated uninflated popped balloon' /* balloons are by default equivalent to one another */ isEquivalent = true /* list balloons in one place as a group */ listWith = [balloonGroup] /* our state index - this is an index into our allStates list */ state = nil /* get our current state - translates our index to a state */ getState = (allStates[state]) /* our list of all of our possible states */ allStates = [balloonStateInflated, balloonStateUninflated, balloonStatePopped] ; class RedBalloon: Balloon 'red -' 'red balloon'; class BlueBalloon: Balloon 'blue -' 'blue balloon'; class GreenBalloon: Balloon 'green -' 'green balloon'; /* use a simple unadorned grouper to keep all the balloons together */ balloonGroup: ListGroupSorted; /* balloon state objects */ balloonStateInflated: ThingState 'inflated' +1 stateTokens = ['inflated'] ; balloonStateUninflated: ThingState 'uninflated' +2 stateTokens = ['uninflated'] ; balloonStatePopped: ThingState 'popped' +3 stateTokens = ['popped'] ; sun: MultiFaceted initialLocationClass = OutdoorRoom instanceObject: Distant { 'sun' 'sun' "It's the yellow star around which the Earth orbits. " } ; /* ------------------------------------------------------------------------ */ /* * Living Room */ redBook: Thing 'red book*books' 'red book' @livingRoom; blueBook: Thing 'blue test book/booklet*books booklets' 'blue test booklet' @livingRoom; bigRedBall: Thing 'big large red ball*balls' 'large red ball' @livingRoom; smallRedBall: Thing 'small little red ball*balls' 'small red ball' @livingRoom; greenBall: Thing 'green ball*balls' 'green ball' @livingRoom; box: Container 'brown cardboard box' 'brown box' @livingRoom; poBox: Thing 'p. o. p.o. po post office # box' 'PO Box' @livingRoom; eightball: Thing '8 ball/8-ball*balls' '8-ball' @livingRoom; greenJar: OpenableContainer 'green glass jar*jars' 'green jar' @livingRoom "It's made of transparent green glass. " material = glass initiallyOpen = nil ; clearJar: OpenableContainer 'clear glass jar*jars' 'clear jar' @livingRoom "It's made of clear glass. " material = glass initiallyOpen = true isEquivalent = true ; CopperCoin location=greenJar; CopperCoin location=greenJar; SilverCoin; SilverCoin; SilverCoin; GoldCoin; GoldCoin; GoldCoin; GoldCoin; GoldCoin; /* achievement for obtaining a gold coin */ goldCoinAchievement: Achievement "obtaining <> gold coin<< scoreCount > 1 ? 's' : ''>>" /* * we're worth two points, and we can be awarded up to five times * (one time per gold coin), for a total of ten points */ points = 2 maxPoints = 10 ; ironKey: Key 'iron key*keys' 'iron key' @livingRoom "It's an ordinary iron key. " ; brassKey: Key 'brass key*keys' 'brass key' @livingRoom "It's an ordinary brass key. " ; rustyKey: Key 'rusty key*keys' 'rusty key' @livingRoom "It's an ordinary rusty key. " ; /* * Note that we don't have to define any vocabulary for the player * character, since the library automatically defines the appropriate * pronouns for any actor being used as the player character. The * pronoun selection is automatic and dynamic, so if we change to * another player character later, the pronouns will all switch * automatically at the same time. */ me: Person location = livingRoom bulkCapacity = 5 // to force lots of bag-of-holding shuffling, for testing // referralPerson = firstPerson /* * When we issue a series of commands to another actor, we can wait * until the entire series finishes before we take another turn. * Set this flag to true to wait, or nil to allow us to take turns * while the other actor carries out our orders. */ issueCommandsSynchronously = true ; + duffel: BagOfHolding, Openable, Container 'duffel bag' 'duffel bag' "It's a really capacious bag. " ; + keyring: Keyring 'key ring/keyring' 'keyring' ; bob: Person 'bob' 'Bob' @livingRoom isProperName = true isHim = true /* obey any command from any other character */ obeyCommand(issuer, action) { return true; } /* * We want to be able to follow other actors if requested, so keep * track of departure data. NPC's don't track departure information * by default, because most NPC's never need it, and tracking it * consumes a little extra time and memory. */ wantsFollowInfo(obj) { return true; } ; /* * Respond to any ASK ABOUT topic with a generic message incorporating * the tokens from the topic. */ + DefaultAskTopic "Ah, yes, <>, very interesting... " ; + GiveTopic matchTopic(fromActor, obj) { /* we match any coin */ return obj.ofKind(Coin) ? matchScore : nil; } handleTopic(fromActor, obj) { /* accept the coin into my actor's inventory */ obj.moveInto(getActor()); /* add our special report */ gTranscript.addReport(new AcceptCoinReport(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(AcceptCoinReport)}, {vec: '\^' + getActor().theName + ' accepts the ' + spellInt(vec.length()) + ' coins. ' }); } ; + AskForTopic [ironKey, brassKey, rustyKey] handleTopic(fromActor, topic) { "So, you want a key, do you? Let's see... "; SimpleLister.showSimpleList(topic.inScopeList + topic.likelyList); "..."; } ; class AcceptCoinReport: MainCommandReport construct(obj) { /* remember the coin we accepted */ coinObj = obj; /* inherit the default handling */ gMessageParams(obj); inherited('Bob accepts {the obj/him}. '); } /* my coin object */ coinObj = nil ; bill: Person 'bill' 'Bill' @livingRoom isProperName = true isHim = true obeyCommand(issuer, action) { return true; } wantsFollowInfo(obj) { return true; } /* * Make Bill go after Bob. This normally wouldn't be important, * since the relative order of actor turns is usually arbitrary, but * we want our test scripts to be stable so we establish a fixed * order. We accomplish the order adjustment simply by giving Bill a * slightly higher schedule order than the default. */ calcScheduleOrder() { inherited(); scheduleOrder += 1; } ; livingRoom: HouseRoom roomName = 'Living Room' destName = 'the living room' desc = "It's a nice, big room. A potted plant is the only attempt at decoration. Passages lead north, east, and west. To the south, the front door of the house leads outside. " north = diningRoom east = den south = frontDoor out asExit(south) west = platformRoom ; + tricycle: Vehicle, BasicChair 'tricycle/trike' 'tricycle' "It's a child's three-wheeled bike. " gaveInstructions = nil allowedPostures = [sitting] /* * Customize the travel arrival/departure messages for certain types * of connectors. The default message is of the form "The tricycle * (carrying whoever) arrives from the east." This isn't great for * the tricycle, which only carries one rider, and which only goes * when the rider makes it go; a better message would be "Bob rides * the tricycle in from the east," which better captures that the * rider is the one making the tricycle go somewhere. */ sayArrivingDir(dir, conn) { local actor = getTravelerActors()[1]; "<> ride<> in from the <> on a tricycle. "; } sayArrivingThroughPassage(conn) { local actor = getTravelerActors()[1]; "<> ride<> in through <> on a tricycle. "; } sayDepartingDir(dir, conn) { local actor = getTravelerActors()[1]; "<> ride<> the tricycle off to the <>. "; } sayDepartingThroughPassage(conn) { local actor = getTravelerActors()[1]; "<> ride<> off through <> on the tricycle. "; } dobjFor(Ride) asDobjFor(SitOn) dobjFor(SitOn) { check() { /* don't allow riding the tricycle except in the house */ if (!gActor.location.getOutermostRoom().ofKind(HouseRoom)) { reportFailure('{You/he} shouldn\'t ride {the dobj/him} except in the house; {it dobj/he} might break. '); exit; } } action() { /* inherit the default handling */ inherited(); /* if we haven't given instructions previously, do so now */ if (!gaveInstructions) { mainReport('Okay, {you\'re} now on the tricycle. {You/he} can probably manage to ride around on it if {it/he} tr{ies}; just say the direction you\'d like ' + (gActor.isPlayerChar ? '' : '{it/him}') + ' to go. '); gaveInstructions = true; } } } dobjFor(Eat) { verify() { } action() { "{You/he} eat{s} {your} tricycle. "; } } ; + frontDoor: Door 'front door' 'front door' "It leads outside to the south. " travelBarrier = [tricycleBarrier, tvBarrier] ; + Decoration 'potted plant small green clay plant/tree/pot/leaf/leaves/dirt/soil' 'potted plant' "It's a tree with small green leaves, about six feet tall and growing in a clay pot filled with soil. " ; + television: TravelPushable 'old huge boxy model/unit/television/tv' 'television' "It's an old, huge, boxy model, certainly black-and-white. The bulbous picture tube shows no sign of life. Two dials control the channel selection (it looks like it's currently <>), but the channel setting hardly matters when the set doesn't work in the first place. It seems to be on small casters, which is a good thing given how big it is and how heavy it looks. " specialDesc = "An old television, a huge, boxy, massive-looking thing, sits on the floor. " specialNominalRoomPartLocation = defaultFloor curChannel() { if (upperDial.curSetting == 'UHF') return lowerDial.curSetting; else return upperDial.curSetting; } ; ++ Decoration 'casters' 'casters' "Small wheels, intended to make it easier to move the unit's huge bulk around the house. " ; ++ Decoration 'bulbous picture tube/screen' 'picture tube' "It shows no pictures. "; ++ upperDial: NumberedDial, Component 'upper tv television channel dial*dials' 'upper dial' "This is the VHF selector dial; it can be set to a channel from 2 through 13, or to the additional stop marked \"UHF\". It's currently set to <>. " minSetting = 2 maxSetting = 13 curSetting = 8 isValidSetting(val) { /* allow the special 'uhf' setting in addition to the numbers */ if (val.toLower() == 'uhf') return true; /* inherit the default handling to check the numbered settings */ return inherited(val); } makeSetting(val) { /* if the setting is 'uhf', change it to all caps */ if (val.toLower() == 'uhf') val = 'UHF'; /* inherit default handling with the (possibly) adjusted value */ inherited(val); } ; ++ lowerDial: NumberedDial, Component 'lower tv television channel dial*dials' 'lower dial' "This is the UHF selector dial; it can be set to a channel from 14 through 82. It's currently set to <>. You probably have to turn the upper dial to \"UHF\" for this dial's setting to have any effect. " minSetting = 14 maxSetting = 82 curSetting = 44 ; + Consultable 'phone book' 'phone book' "You can probably look up people you know in here. " ; ++ ConsultTopic @bob "Bob's number is (415)555-1212. "; ++ ConsultTopic @bill "Bill's number is (650)555-1212. "; ++ ConsultTopic @salesman "Ron's number is (510)555-1212. "; ++ ConsultTopic @insCompany "Actuarial Life Insurance Company! Call for best rates! (800)555-1212! " ; ++ DefaultConsultTopic "You can't find any such listing. "; /* ------------------------------------------------------------------------ */ /* * Platform room */ platformRoom: HouseRoom 'Platform Room' 'the platform room' "This room is obviously highly contrived. Filling one end of the room is a platform, almost like a theater stage, raised a couple of feet above the floor. At opposite ends of the platform are two smaller platforms, one red and one blue; atop each smaller platform is a chair of the same color as its platform. In the center of the main platform is a raised dais, upon which <> a leather armchair. <.p>The only exit is east. " east = livingRoom down = (whiteBox.isOpen ? whiteBox.down : nil) ; + Fixture, Platform 'main theater theatre platform/stage*platforms' 'main platform' ; ++whiteBox: Openable, Booth 'large white cardboard box' 'white box' useInitSpecialDesc() { /* show our initial description only when the actor isn't in me */ return inherited() && !gActor.isIn(self); } initSpecialDesc = "On the main platform is a large white box, about waist high and quite roomy. " /* don't mention our contents if we're using our initial description */ contentsListed = (!useSpecialDesc()) interiorDesc = "The box is roomy, but not enough that you can stand up in it when it's closed. " down = whiteBoxTrapDoor /* make it paper, so we can hear through the box */ material = paper /* * we can't stand up in the box when it's closed, so make the * default posture sitting */ defaultPosture = (isOpen ? standing : sitting) /* we can't close the box when someone's standing in it */ makeOpen(stat) { /* if we're closing the box, check to make sure we're allowed to */ if (!stat) { /* * We're trying to close the box; if anyone's standing in * the box, don't allow it. */ foreach (local cur in allContents()) { /* if this is a standing actor, disallow closure */ if (cur.isActor && cur.posture == standing) { /* * we can't close - issue a failure report and * terminate the command */ reportFailure('{You/he} cannot close the box while anyone is standing in it. '); exit; } } } /* no problems - inherit default handling */ inherited(stat); } /* * if they try to stand within a nested room within us, mention that * they can only sit */ roomBeforeAction() { /* * If we're closed, and the action is 'stand', and they're * something nested within me, mention after the fact that * there's no room to stand up. We need to mention this, but we * do not need to enforce the constraint here because the nested * room will set our default sitting posture when moving an * actor from an interior location. */ if (!isOpen && gActionIs(Stand) && gActor.isIn(self) && !gActor.isDirectlyIn(self)) reportAfter('There isn\'t room to stand up in here, so {you/he} sit{s} in the box. '); } /* enforce the low headroom when the box is closed */ makeStandingUp() { if (isOpen) { /* we're open, so proceed as normal */ inherited(); } else { /* the box is closed, so they can't stand up */ reportFailure('There\'s not enough room to stand up in the box while it\'s closed. '); } } ; +++ whiteBoxTrapDoor: Fixture, Door -> tunnelTrapDoor 'small trap door' 'trap door' "It's set into the floor of the box; it looks just large enough for you to fit through. " /* * list me *after* the room's contents, since the box is sometimes * listed with the room's contents */ specialDescBeforeContents = nil initSpecialDesc = "A small trap door is set into the floor of the box. " /* don't require standing up before entering the trap door */ actorTravelPreCond(actor) { return []; } enteredBefore = nil dobjFor(TravelVia) { action() { /* mention the ladder below */ if (enteredBefore) "You find the ladder below and climb down. "; else { "You tentatively lower yourself through the door, and manage to find what feels like a ladder below, so you carefully climb down. "; enteredBefore = true; } /* inherit the default handling */ inherited(); } } ; +++ Switch 'small black plastic beeper pager box/beeper/pager/switch' 'beeper' "It's a small black plastic box with a single red light (which is currently <>) and a switch (currently <>). " /* * if they know about us, they'll recognize our beeping, so they'll * want to be able to refer to the beeper itself; thus, give the * beeper a sound presence once it's been seen */ soundPresence = (isOn && seen) isOn = true makeOn(val) { /* inherit the default handling */ inherited(val); /* add/remove our beeping sound, as appropriate */ beeperNoise.moveInto(val ? self : nil); } ; ++++ Component 'tiny small red beeper light' 'beeper light' "It's a tiny red light, currently <>. " ; ++++ beeperNoise: Noise 'piercing high-pitched high pitched beeping sound/noise/beep' 'beeping sound' sourceDesc = "The beeper is making a piercing, high-pitched beeping noise. " descWithSource = "The beeper is beeping, which is what they do. " descWithoutSource = "It's a piercing, high-pitched beeping noise. " hereWithSource = "The beeper is making a piercing beeping noise. " hereWithoutSource = "You can hear a high-pitched beeping noise. " displaySchedule = [2, 4] ; ++ Fixture, Platform 'small smaller red platform*platforms' 'red platform' ; class PlatformChair: Chair dobjFor(SitOn) { verify() { /* inherit default */ inherited(); /* * if they're directly in my location, and my location is a * nested room of some kind, boost likelihood that this is * where they want to sit */ if (gActor.isDirectlyIn(location) && location.ofKind(NestedRoom)) logicalRank(150, 'adjacent'); } } ; +++ Fixture, PlatformChair 'red chair' 'red chair' ; ++ Fixture, Platform 'small smaller blue platform*platforms' 'blue platform' ; +++ Fixture, PlatformChair 'blue chair' 'blue chair' ; ++ dais: Fixture, Platform 'raised dais' 'dais' ; +++ stool: PlatformChair 'wooden wood stool' 'stool' /* * since we're part of the main room description if we're in our * original location, don't list me if I'm on the dais */ isListed = (location == dais ? nil : inherited) ; +++ Fixture, PlatformChair 'leather arm chair/armchair' 'armchair' actorInPrep = 'in' ; #if 0 // disabled by default /* * enable this to test multi-location sense connections - this is a bit * too contrived an example, so we disable it by default */ whiteBoxHole: SenseConnector, Fixture 'small hole' 'small hole' "It's a small hole in the box. " locationList = [whiteBox, backYard] connectorMaterial = glass ; #endif /* ------------------------------------------------------------------------ */ /* * Front yard */ frontYard: OutdoorRoom 'Front Yard' 'the front yard' "This is a small yard in front of a modest house. The front door to the house leads in to the north. To the south is the street. " in asExit(north) north = frontDoorOutside south: NoTravelMessage { "You'd rather not venture beyond the immediate vicinity of the house right now. " } atmosphereList: RandomEventList { eventList = [ nil, 'A car drives past. ', 'Someone honks a horn in the distance. ', nil, 'A few birds pass overhead. ', 'A couple of bicyclists pedal past on the street. ', nil, nil ] } ; + frontDoorOutside: Door ->frontDoor 'front door' 'front door' "It leads north, into the house. " ; + Enterable, Decoration 'narrow residential street/road' 'street' "It's a narrow residential street. " connector = (frontYard.south) ; + Enterable, Decoration ->frontDoorOutside 'house' 'house' "It's a modest house. A door leads in to the north. " ; + RedBalloon state = 3; + RedBalloon state = 3; + RedBalloon state = 3; + RedBalloon state = 3; + RedBalloon state = 1; + RedBalloon state = 1; + RedBalloon state = 2; + BlueBalloon state = 2; + BlueBalloon state = 1; + BlueBalloon state = 1; + GreenBalloon state = 3; + GreenBalloon state = 3; + GreenBalloon state = 3; + GreenBalloon state = 3; + GreenBalloon state = 3; + salesman: Person 'salesman/man/ron' 'salesman' "He's a thin, youngish man in a garish suit that's rather comically oversized on him. " isHim = true ; ++ Wearable 'oversized garish suit' 'suit' "It's an oddly bright shade of blue, and is way too large for its present wearer. " wornBy = salesman ; ++ Thing 'huge black briefcase' 'briefcase' "It's a huge black briefcase. " ; /* the salesman's initial state */ ++ ActorState isInitState = true specialDesc = "A youngish man in a garish suit is standing in the yard near the front door. " takeTurn() { if (gPlayerChar.location == frontYard) salesman.initiateConversation(salesConv, 'sales-hello'); inherited(); } ; ++ salesConv: InConversationState attentionSpan = 10000 nextState = salesWaiting stateDesc = "He's smiling eagerly at you. " specialDesc = "The salesman is standing a little too close, smiling a bit too much. " ; ++ salesWaiting: ConversationReadyState specialDesc = "The salesman is standing in the yard. " inConvState = salesConv takeTurn() { /* re-arm for conversation only when the PC is gone */ if (gPlayerChar.location != frontYard) regreet = true; /* greet again randomly if we're re-armed and the PC is present */ if (regreet && gPlayerChar.location == frontYard && rand(100) < 33) salesman.initiateConversation(salesConv, 'sales-1'); inherited(); } /* flag: we're ready to re-greet the player */ regreet = nil /* on activation, set the re-greet flag to nil by default */ activateState(actor, oldState) { inherited(actor, oldState); regreet = nil; } ; +++ HelloTopic "You tap the salesman on the shoulder. Oh, hi! he says. I\'d sure like to talk to you about life insurance. <.convnode sales-1> " ; +++ ByeTopic, ShuffledEventList ['I have to go, you say. <.p>Thanks for your time! the salesman says. ', 'Thanks, but not right now, you say. <.p>I\'ll be here when you\'re ready! the salesman says. '] ; +++ ImpByeTopic, ShuffledEventList ['The salesman waves. Okay, I\'ll wait here! he says. ', 'Thanks for your time! the salesman says. ', 'The salesman calls after you, I\'ll be here if you need me! '] ; ++ ConvNode 'sales-hello' npcGreetingMsg = "<.p>The man in the bad suit walks up to you and extends his hand; by habit you shake his hand. Hello, friend! My name is Ron, and I represent the Acturial Life Insurance Company. He releases your hand after a vigorous shaking. You know, a lot of people I talk to don't know just how important life insurance is to proper financial planning. Let me ask you this: do you have all the life insurance coverage you and your family need? " npcContinueList: CyclicEventList { [ 'The salesman says eagerly, Really, do you have enough insurance? ', 'Ron says, The sad fact is, most people don\'t know how much insurance they really need. I\'d really like to tell you about our policies...<.convnode sales-1> ' ]} ; +++ YesTopic "I'm covered, you say. <.p>Ron smiles and nods. You know, a lot of people think they're covered. But have you really read your insurance policy? Most insurance policies have so many exclusions and limitations that you just can't rely on them. That's why Acturial Life created a new kind of insurance policy. Let me tell you about it...<.convnode sales-1> " ; +++ NoTopic "Why, no, you say. <.p>Ron smiles eagerly. Well, then it's a good thing I was in your neighborhood today! Let me tell you about our policies... <.convnode sales-1> " ; ++ ConvNode 'sales-1' npcGreetingMsg = "<.p>Ron walks up to you. Hello again, friend! It would be my pleasure to help you with your insurance needs. " npcContinueList: ShuffledEventList { ['The salesman looks serious for a moment. Most people don\'t know this, but death is our number one killer, he says earnestly. He goes back to smiling. Fortunately, death is covered under our policy\'s loss-of-life section. ', 'Ron says, You know, life insurance is a lot more interesting than most people think. ', 'The salesman says, I\'m really glad I was in the neighborhood today. Everyone needs more insurance than they think. ' ]} ; ++ AskTellTopic, StopEventList @insPolicy [ 'Okay, tell me about your policy, you say. <.p>Our policy is the best in the business, Ron says. For starters, it\'s guaranteed go pay, which most policies aren\'t. There\'s so much more I can tell you. ', 'Tell me more about your policy, you say. <.p>I could go on all day, the salesman says. ' ] ; ++ AskTellTopic, StopEventList @insCompany ['I\'ve never heard of your company, you say. <.p>Most people haven\'t, Ron says. We\'re the best-kept secret in the business.', 'Why would you want to keep your company a secret? <.p>One word: savings. We save on marketing expenses, and pass the low cost on to you. ', 'What else can you tell me about your company? <.p>I\'d rather tell you about the policy. '] ; /* some topics for the salesman to talk about */ insPolicy: Topic 'life insurance policy/policies'; insCompany: Topic 'acturial life insurance company'; /* ------------------------------------------------------------------------ */ /* * Den */ den: HouseRoom roomName = 'Den' destName = 'the den' desc = "This small room is dominated by a desk, a massive steel edifice painted a drab gray, something that would be more at home in a government office during the cold war. Behind the desk, built in to the north wall, is a bookcase. A passage leads west. " west = livingRoom north = bookcasePassage roomParts = static (inherited() - defaultNorthWall) ; + Decoration 'north wall*walls' 'north wall' "A floor-to ceiling bookcase<< bookcasePassage.isOpen ? " (which has swung aside to expose a passage to the north)" : "">> is built into the wall. " ; + bookcase: Fixture 'book case/bookcase' 'bookcase' desc { "It's a floor-to-ceiling bookcase built in to the north wall, behind the desk, filled with hundreds of dusty tomes. "; if (bookcasePassage.isOpen) "The bookcase is evidently a secret door, because it has moved sideways to expose a passage to the north. "; } ; + bookcasePassage: BasicOpenable, HiddenDoor 'passage' 'passage' "It leads north. " initSpecialDesc { if (isOpen) "The bookcase has moved aside to reveal a passage to the north. "; } ; + Decoration 'dusty book/books/tome/tomes' 'books' "There must be hundreds of books, all with incomprehensible titles and authors you've never heard of. " isPlural = true dobjFor(Read) { verify() { } action() { "Much as you enjoy reading, you can't find any book whose title even means anything to you, let alone holds any interest. "; } } ; + desk: Immovable, Surface 'massive big huge edifice drab gray grey steel metal desk' 'desk' desc { "It's a big desk with a single drawer (currently <>). A small button is set inconspicuously into the edge of the desktop"; if (hiddenPanel.isOpen) ", and in the side is a small compartment containing a lever"; ". "; } dobjFor(Open) remapTo(Open, deskDrawer) dobjFor(Close) remapTo(Close, deskDrawer) dobjFor(LookIn) remapTo(LookIn, deskDrawer) iobjFor(PutIn) remapTo(PutIn, DirectObject, deskDrawer) ; ++ Thing 'bob\'s brand premium fish cleaner jar/cleaner' 'jar of Bob\'s Brand Premium Fish Cleaner' "It presumably once held fish cleaner (whatever that is), but it's just an empty jar now. " ; ++penCup: Container 'pen cup' 'pen cup' "It's a uneven clay cup, currently employed as a pen holder. " /* just for fun, show my contents out-of-line in listings I'm in */ contentsListedSeparately = true ; class pen: Thing 'pen*pens' 'pen' "A simple Bic. " isEquivalent = true ; +++pen; +++pen; +++pen; +++pen; ++ deskDrawer: Component, OpenableContainer 'desk drawer' 'desk drawer' ; ++ Immovable 'old-fashioned rotary phone/telephone/dial/receiver/handset' 'phone' "It's an old-fashioned rotary phone, very sturdy-looking but rather scuffed from long use. " dobjFor(Take) { verify() { } check() { } action() { "You pick up the handset, but there's nothing but static on the line. At least it stops ringing. You put the handset back, and the phone starts ringing again. "; } } dobjFor(Answer) asDobjFor(Take) ; +++ Noise 'ring/ringing' 'ringing/bell' sourceDesc = "The phone is ringing loudly. " descWithSource = "It's an actual mechanical bell, not the ubiquitous electronic warble of modern phones. " hereWithSource() { switch (displayCount) { case 1: "The phone is ringing. "; break; default: "The phone is still ringing. "; break; } } displaySchedule = [2, 4, 8] ; ++ Button, Component 'small button' 'small button' dobjFor(Push) { action() { /* open/close the hidden panel */ hiddenPanel.makeOpen(!hiddenPanel.isOpen); /* show the appropriate message */ if (hiddenPanel.isOpen) "A previously hidden panel in the side of the desk opens, revealing a small compartment containing a lever. "; else "The panel in the desk closes, hiding the lever. Now that the panel is closed, it's completely seamless - you can't see any sign of it. "; } } ; ++ hiddenPanel: BasicOpenable, Component, Container 'hidden panel/compartment' 'compartment' "It's a small compartment, just large enough to contain the lever it enclosed. " bulkCapacity = 0 initiallyOpen = nil /* the panel is only visible when it's open */ sightPresence = (self.isOpen) ; +++ SpringLever, Component 'lever' 'lever' "It's mounted in a small compartment in the desk. " dobjFor(Pull) { action() { /* open/close the secret door */ bookcasePassage.makeOpen(!bookcasePassage.isOpen); /* show the appropriate message */ if (bookcasePassage.isOpen) "The lever is surprisingly heavy, but you manage to pull it all the way out. At the end of its travel, something under the floor clicks, and the bookcase behind the desk slides sideways far enough to open a passage to the north. As soon as you release the lever, it springs back to its original position. "; else "You pull the lever out all the way. Something under the floor clicks, and the bookcase slides sideways until it covers the passage, leaving no trace of a doorway. "; } } ; ++ typewriter: Immovable 'big old old-fashioned black manual typewriter' 'typewriter' initSpecialDesc = "A big, old-fashioned manual typewriter is sitting on the desk. " desc = "It's big, black, manual typewriter, like something out of an old black-and-white movie about newspapermen or private detectives. There's a piece of paper in it. " dobjFor(TypeOn) { verify() { } } dobjFor(TypeLiteralOn) { verify() { } action() { /* add the literal text to the paper */ typewriterPaper.addText(gAction.getLiteral()); mainReport('With some effort, {you/he} work{s} the keys of the ancient machine, noisily impressing ' + gAction.text_ + ' on the paper. '); } } ; +++ typewriterPaper: Thing 'piece/paper' 'piece of paper' isListedInContents = nil desc { if (textList.length() == 0) "The paper is blank. "; else { "The typewritten letters on the page have that uneven darkness and wandering alignment characteristic of the work a well-worn manual typewriter:\b"; foreach (local cur in textList) "\t<>\n"; ""; } } addText(txt) { textList.append(txt); } textList = static new Vector(10) moveInto(obj) { reportFailure('On second thought, {you/he}\'d rather leave the paper in the typewriter in case someone needs to do some typing. '); exit; } dobjFor(TypeOn) { verify() { } } dobjFor(TypeLiteralOn) { verify() { /* * allow it but reduce the likelihood - we want the * typewriter to win in a default case */ logicalRank(50, 'nondefault'); } action() { /* treat 'type on paper' as 'type on typewriter' */ replaceAction(TypeLiteralOn, typewriter, gAction.text_); } } ; ++ dagger: Thing 'dagger' 'dagger' initSpecialDesc = "Someone has jabbed a dagger into the top of the desk, leaving the dagger sticking up almost vertically. " initExamineDesc = "Its point is stuck into the top of the desk. " ; ++ watch: Wearable 'watch' 'watch' "It has a minute hand and an hour hand. "; +++ Component 'minute big hand' 'minute hand'; +++ Component 'hour little hand' 'hour hand'; ++ Container 'glass bottle' 'bottle' bulkCapacity = 2 canFitObjThruOpening(obj) { /* we can only fit small objects through the opening */ return obj.bulk < 2; } ; +++ Thing 'minature model sailing ship/boat' 'model ship' "It's a miniature model of a sailing ship. " bulk = 2 ; #if 0 // for testing distant viewing of sightSize=large objects viewScreen: SenseConnector, Fixture 'view screen/viewscreen' 'viewscreen' "It's a viewscreen, displaying a close-up shot of a floating sphere. " locationList = [den, backYard] connectorMaterial: Material { seeThru = distant hearThru = distant smellThru = opaque touchThru = opaque } ; #endif /* ------------------------------------------------------------------------ */ /* * Top of stairs */ class SpiralStairway: Fixture 'perforated central black metal/stair/stairs/stairway/pole' 'stairs' "The stairs are narrow triangles of perforated black metal arranged in a helix around a central pole. " isPlural = true ; topOfStairs: HouseRoom roomName = 'Top of Stairs' destName = 'the top of the stairs' desc = "This is the top of a narrow spiral staircase that leads down a dark shaft walled in rough brown bricks. A passage leads south. " south = stairPassage down = spiralStairsTop ; + Decoration 'dark rough brown shaft/brick/bricks' 'dark shaft' "The shaft is just large enough for the staircase. " ; + stairPassage: ThroughPassage ->bookcasePassage 'south passage' 'passage' ; + spiralStairsTop: StairwayDown, SpiralStairway moveActor(actor) { if (actor.isPlayerChar) "{You/he} carefully descend{s} the steep, narrow stairs. "; inherited(actor); } travelBarrier = static [ new tvBarrier('The television is far too heavy to take down the stairs. '), new tricycleBarrier('It would make a nice stunt, but it\'s far too dangerous to ride the tricycle down the stairs. ') ] ; /* ------------------------------------------------------------------------ */ /* * Bottom of stairs */ bottomOfStairs: DarkHouseRoom roomName = 'Bottom of Stairs' destName = 'the bottom of the stairs' desc = "This is the bottom of a narrow spiral staircase. Apart from the stairs, the only exit is the door to the south. " up = spiralStairsBottom south = stairDoor /* no illumination here */ brightness = 0 /* * even in the dark, keep the stairs in scope, because we're * standing on them */ getExtraScopeItems(actor) { return inherited(actor) + spiralStairsBottom; } ; class IronDoor: LockableWithKey, Door 'iron door/rivet/rivets/plate/plates' 'iron door' "It's a formidable-looking mass of iron plate and rivets. " keyList = [ironKey] ; + stairDoor: IronDoor; + spiralStairsBottom: StairwayUp, SpiralStairway -> spiralStairsTop travelBarrier: tricycleBarrier { msg_ = 'Riding the tricycle up the stairs is simply out of the question. ' } ; /* * make the washing machine a complex containe so that we can have * components as well as contents within */ + ComplexContainer 'washing machine/washer' 'washing machine' "It's a beat-up old washing machine. A big dial is the only obvious control. " subContainer: ComplexComponent, OpenableContainer { } ; ++ Component, LabeledDial 'big washer washing machine control dial' 'control dial' "The dial has stops labeled Wash, Rinse, Spin, and Stop. It's currently set to <>. " curSetting = 'Wash' validSettings = ['Wash', 'Rinse', 'Spin', 'Stop'] ; /* ------------------------------------------------------------------------ */ /* * Dining Room */ diningRoom: HouseRoom roomName = 'Dining Room' destName = 'the dining room' desc = "This medium-sized room<> has a dining table and a couple of chairs, one upholstered in dark fabric and other in light fabric. A door (<>) leads north. " south = livingRoom north = diningDoor out asExit(north) myNote: Footnote { "This is just a sample note for the dining room. " } ; + alcove: OutOfReach, Fixture, Container 'arched small alcove' 'small alcove' "It's a small, arched alcove, set high up on the wall, near the ceiling. " useSpecialDesc = (contents.length() != 1 || contents[1] != trophy) specialDesc = "A small alcove is set near the top of one wall, up near the ceiling. " canObjReachContents(obj) { /* if the actor is standing on a chair, allow it */ if (obj.posture == standing && obj.location == diningTable) return true; /* inherit the default */ return inherited(obj); } cannotReachFromOutsideMsg(obj) { return '{You/he} can\'t reach ' + obj.theNameObj + ' from here; the alcove is too high up. {You/he} might be able to reach the alcove if {you/he} were standing on something. '; } ; ++ trophy: Thing 'silver big trophy/award/cup' 'trophy' "It's shaped like a big cup, and looks to be made of silver. " useInitSpecialDesc = (location == alcove) initSpecialDesc = "A trophy is visible in a small alcove set into the top of one wall. " ; + diningDoor: Door 'door' 'door' "It leads north. " travelBarrier = [tricycleBarrier, tvBarrier] ; class DiningChair: Chair, Immovable 'chair*chairs' 'chair' "Its style matches that of the table. " ; + DiningChair 'light lighter fabric' 'lighter chair'; + DiningChair 'dark darker fabric' 'darker chair'; + diningTable: Heavy, Platform 'dining large oval table' 'table' "It's a large<> oval table. " myNote: Footnote { "Okay, it's not all that large. Medium-sized sounds so weak, though. " } ; class MyCandle: FireSource, Candle fuelLevel = 32 desc() { /* show a description based on how far we've burned down */ if (fuelLevel > 30) "The candle has barely been used. "; else if (fuelLevel > 24) "The candle looks only slightly burned down. "; else if (fuelLevel > 16) "The candle looks about half burned down. "; else if (fuelLevel > 8) "The candle is considerably burned down. "; else if (fuelLevel > 0) "The candle is mostly burned down. "; else "The candle is completely burned down. "; /* if we're burning, mention it */ if (isLit) "It's lit. "; } ; ++ MyCandle 'red candle' 'red candle'; ++ MyCandle 'white candle' 'white candle'; ++ vase: Container 'vase' 'vase' "It's a simple glass cylinder, open at the top. " bulkCapacity = 1 initSpecialDesc = "A bouquet of flowers is arranged in a vase in the center of the table. " useInitSpecialDesc() { /* don't use the initial description if the flowers have been moved */ return flowers.isIn(self) ? inherited() : nil; } verifyInsert(obj, newCont) { if (obj != flowers) illogical('The vase is only designed to hold flowers. '); } /* * in room and inventory lists, show the vase with its flowers, if * it has the flowers */ listName = (flowers.isIn(self) ? 'a bouquet of flowers in a vase' : inherited()) ; +++ flowers: Thing 'flower/flowers/arrangement/bouquet' 'bouquet of flowers' "It's a nice arrangement of flowers. " /* * don't separately list the flowers if they're in the vase, because * the vase shows the flowers as part of its list name; but don't * hide the flowers from an explicit "look in vase" */ isListed = (!isIn(vase)) isListedIn = true ; ++++ Odor 'floral flowery pleasant fragrance/odor/smell' 'floral fragrance' /* the description when they smell the flowers directly */ sourceDesc = "The flowers have a strong, well, flowery fragrance. " /* the description when they smell us and the flowers are visible */ descWithSource = "The pleasant fragrance is coming from the flowers. " /* the description when they smell us and the flowers aren't seen */ descWithoutSource = "It's a pleasant floral fragrance. " /* room description with and without being able to see the flowers */ hereWithSource = "The flowers have a pleasant fragrance. " hereWithoutSource = "A flowery fragrance is in the air. " ; ++ StretchyContainer 'stretchy bag' 'stretchy bag' "It's made of some very elastic material. It seems to take on the size and shape of whatever's inside. " /* * we have no bulk contribution of our own when we have any contents, * but we do have a minimum empty bulk of 1 */ bulk = 0 minBulk = 1 ; ++ Container 'cookie tin' 'cookie tin' "It's a round metal cylinder about three inches tall, with no lid, decorated with pictures of cookies. " bulkCapacity = 3 ; ++ Thing 'yellow pile inflatable rubber raft' 'inflatable rubber raft' desc { if (inflated) "It's fully inflated. "; else "In its current deflated state, it's just a pile of yellow rubber, bunched up messily. It's large, but you could probably inflate it if you had to. "; } inflated = nil bulk { return inflated ? 10 : 2; } makeInflated(flag) { /* check the effect of the inflation on my bulk */ whatIf({: checkBulkChange()}, &inflated, flag); /* set the new status */ inflated = flag; } dobjFor(Inflate) { verify() { if (inflated) illogicalAlready('It\'s already fully inflated. '); } action() { makeInflated(true); mainReport('It takes all your strength, but you manage to blow up the raft. You might feel light-headed for a bit. '); } } dobjFor(Deflate) { verify() { if (!inflated) illogicalAlready('It\'s already fully deflated. '); } action() { makeInflated(nil); mainReport('You let the air out of the raft, which flattens into a bunched-up pile of yellow rubber. '); } } ; /* ------------------------------------------------------------------------ */ /* * Back Yard */ backYard: OutdoorRoom roomName = 'Back Yard' destName = 'the back yard' vocabWords = 'back yard' desc = "This small yard has a well-kept lawn. The edges of the yard are defined by thick, tall hedges. An old garden shed occupies the northwest corner of the yard. A door into the house is to the south. " south = yardDoor in asExit(south) northwest = shedOuterDoor ; + yardDoor: Door ->diningDoor 'house back door' 'back door of the house' "It leads south, into the house. " ; + Decoration 'shrub/shrubs/hedge/hedges/plant/plants' 'hedges' "The hedges are tall and thick, and completely close in the boundaries of the yard. " isPlural = true dobjFor(LookOver) { verify() { } action() { "The hedges are too tall. "; } } ; /* * This object tests and demonstrates using '*' and matchName to parse * non-dictionary words as object names. * * This isn't the kind of place you'd really use '*' in practice; for * this case, you'd be much better off defining all of the possible * color names as adjectives for the object and then using matchName() * to ignore matches to anything but the current one. This works better * because the color names would then exist as dictionary words, and * hence the parser would not complain about them being unknown when * this object is not in scope. */ + Sphere: Immovable noun = '*' 'sphere' name = (colors[curColor] + ' sphere') desc = "The <> sphere floats unnervingly in mid-air, with no apparent mechanical means of suspension. It does not hover like a balloon, but seems absolutely still, as though firmly attached to an invisible pole. " cannotTakeMsg = 'The sphere is strangely fixed in place; it resists your attempts to move it. It does seem to be able to rotate freely on its vertical axis, though, so you could probably turn it if you wanted to. ' cannotMoveMsg = (cannotTakeMsg) cannotPutInMsg = (cannotTakeMsg) initSpecialDesc = "In the center of the yard, floating in mid-air, is a <> sphere. " matchNameCommon(origTokens, adjustedTokens) { /* check each token */ foreach (local cur in origTokens) { /* * if it's 'sphere' or our current color name, match it; * otherwise, we don't have a match */ if (getTokVal(cur) not in ('sphere', colors[curColor])) return nil; } /* all of our tokens match */ return self; } dobjFor(Turn) { verify() { } action() { ++curColor; if (curColor > colors.length()) curColor = 1; "Although the sphere is immovably fixed in space, it rotates so freely that it feels weightless. You give it the lightest touch, and it starts spinning wildly, so fast that it becomes a featureless, glowing blur. After a few moments, it abruptly comes to a stop, and you see that its color has changed to <>. "; } } /* current index in color list */ curColor = 1 /* list of possible colors */ colors = ['red', 'blue', 'green', 'orange', 'yellow', 'white'] ; + Enterable, Decoration ->yardDoor 'house' 'house' "It's a modest one-story house. A door leads in to the south. " ; + Enterable ->shedOuterDoor 'old garden shed' 'shed' "The shed's wood siding looks well weathered; only the thinnest layer of white paint remains. Its flimsy-looking door is <>. " ; + Decoration 'shed\'s wood siding' 'wood siding' "It looks weathered. " ; + shedOuterDoor: Door 'shed door' 'door of the shed' "It leads into the shed. " ; /* ------------------------------------------------------------------------ */ /* * Shed */ shed: Room 'Shed' 'the shed' "There's barely room to stand up or turn around in this tiny storage space. The north wall has a few deep shelves, but otherwise the structure is featureless. A door leads out to the southeast. " southeast = shedInnerDoor out asExit(southeast) ; + shedInnerDoor: Door ->shedOuterDoor 'shed door' 'door' "It leads out of the shed. " ; + shedShelves: Fixture, Surface 'deep shelf/shelves' 'shelf' "The shelves are about a yard deep and look sturdy. " ; ++ Flashlight 'flash light/flashlight' 'flashlight' "It's a big lantern-type flashlight. It's currently <>. " ; ++ Thing 'rat poison/box' 'box of rat poison' "It's a large box, missing its top, full of white powder. The box is labeled RAT POISON and is marked with a prominent skull-and-crossbones symbol, along with a legend in tiny type reading Use of this product in a manner inconsistent with its labeling is a violation of Adventure Game Law. " dobjFor(Eat) remapTo(Eat, ratPoison) lookInDesc = "It's full of white powder. " ; +++ ratPoison: Component, Food 'white powder' 'white powder' "The box is full of white powder. " cannotTakeMsg = 'You\'d best leave the poison in the box. ' cannotMoveMsg = 'You\'d best leave the poison in the box. ' cannotPutMsg = 'Best to leave the poison in the box. ' dobjFor(Pour) { verify() { } action() { "You'd best leave the poison in its box. "; } } dobjFor(PourInto) asDobjFor(Pour) dobjFor(PourOnto) asDobjFor(Pour) dobjFor(Eat) { preCond = [touchObj] verify() { dangerous; } action() { "It's not very tasty, but it sure is deadly. "; /* terminate the game */ finishGameMsg(ftDeath, [finishOptionUndo, finishOptionCredits]); } } ; class MyMatch: Matchstick 'match*matches' 'match' ; ++ Matchbook 'match book/matches/matchbook*matches' 'book of matches' matchName(origTokens, adjustedTokens) { /* * if the name is just 'match', ignore it - we want to allow * 'match' as an adjective because we want to match 'match * book', but just plain 'match' is very unlikely to mean us */ if (origTokens.length() == 1 && getTokVal(origTokens[1]) == 'match') return nil; /* inherit default handling */ return inherited(origTokens, adjustedTokens); } /* put this after the individual matches when we match a plural */ pluralOrder = 110 ; +++ MyMatch; +++ MyMatch; +++ MyMatch; +++ MyMatch; +++ MyMatch; /* ------------------------------------------------------------------------ */ /* * Additional verbs */ DefineTAction(Inflate); VerbRule(Inflate) ('inflate' | 'blow' 'up') dobjList | 'blow' dobjList 'up' : InflateAction verbPhrase = 'inflate/inflating (what)' ; DefineTAction(Deflate); VerbRule(Deflate) 'deflate' dobjList : DeflateAction verbPhrase = 'deflate/deflating (what)' ; DefineTAction(LookOver); VerbRule(LookOver) 'look' 'over' dobjList : LookOverAction verbPhrase = 'look/looking (over what)' ; DefineTAction(Ride); VerbRule(Ride) 'ride' singleDobj : RideAction verbPhrase = 'ride/riding (what)' ; DefineTAction(Answer); VerbRule(Answer) 'answer' dobjList : AnswerAction verbPhrase = 'answer/answering (what)' ; modify Thing dobjFor(Inflate) { preCond = [touchObj] verify() { illogical('{That dobj/he} {is}n\'t something {you/he} can inflate. '); } } dobjFor(Deflate) { preCond = [touchObj] verify() { illogical('{That dobj/he} {is}n\'t something {you/he} can deflate. '); } } dobjFor(LookOver) { preCond = [objVisible] verify() { illogical('{You/he} see{s} nothing unusual. '); } } dobjFor(Ride) { verify() { illogical('{That dobj/he} {is}n\'t something {you/he} can ride. '); } } dobjFor(Answer) { preCond = [objVisible] verify() { illogical('{The dobj/he} didn\'t ask {you/him} anything. '); } } ; DefineTopicAction(ThinkAbout) execAction() { "You think about <>. "; } ; VerbRule(ThinkAbout) 'think' 'about' singleTopic : ThinkAboutAction verbPhrase = 'think/thinking (about what)' ; /* ------------------------------------------------------------------------ */ /* * Basement tunnel */ tunnel: DarkRoom 'Tunnel' 'the tunnel' "This low, narrow tunnel is walled in old, dark gray concrete or something similar. The walls curve inward at shoulder level to form an arched ceiling just barely high enough at the center to allow walking without bending your neck. The passage ends to the north at an iron door, and ends at the south in an earthen wall. A narrow side passage leads west. " north = tunnelDoor west = sideTunnel /* * since we have special walls and ceiling that are specifically * included as normal decoration objects in this location, keep only * the default floor among our room parts */ roomParts = [defaultFloor] ; + tunnelCeiling: Decoration 'arched concrete cement ceiling roof' 'ceiling' "It's very low, and arches in from the walls. " ; + tunnelWalls: Decoration 'east west tunnel concrete cement wall/walls' 'tunnel wall' "The walls are made of some kind of dark gray concrete. " ; + earthenWall: Decoration 'earthen dirt south wall' 'earthen wall' "It's just a wall of dirt, as though construction on the tunnel had been abruptly halted here. " ; + tunnelDoor: IronDoor ->stairDoor; /* ------------------------------------------------------------------------ */ /* * Side tunnel */ sideTunnel: DarkRoom 'Jagged Tunnel' 'the jagged tunnel' "This is an extremely narrow tunnel; it looks like construction was abandoned before it was completed. The passage is jagged, but continues a little way to the east. A rough ladder has been improvised from wood scraps and fastened to the wall, leading up to a small trap door above. " up = tunnelTrapDoor east = tunnel roomParts = [defaultFloor] getExtraScopeItems(actor) { /* * if they arrived from above, make sure the trap door and * ladder are in scope */ if (lastArrivedVia[actor] == tunnelTrapDoor) return inherited(actor) + tunnelTrapDoor + tunnelLadder; else return inherited(actor); } /* table giving last arrival connector for each actor present */ lastArrivedVia = static new LookupTable(4, 4) travelerArriving(traveler, origin, connector, backConnector) { /* note the connector by which they're arriving */ foreach (local actor in traveler.getTravelerActors()) lastArrivedVia[actor] = backConnector; /* inherit default handling */ inherited(traveler, origin, connector, backConnector); } ; + tunnelTrapDoor: Fixture, Door 'small trap door' 'trap door' ; + tunnelLadder: StairwayUp 'rough improvised wood ladder/scraps' 'ladder' dobjFor(TravelVia) remapTo(TravelVia, tunnelTrapDoor) ; + Decoration 'west north south tunnel dirt jagged wall/walls' 'tunnel wall' "The walls are just bare dirt. " ; /* ------------------------------------------------------------------------ */ /* * A generic USE verb of two objects. We'll remap this to a more * specific verb when possible, based on the direct object, using the * direct object's remapDobjUse() method. */ DefineTIAction(UseWith) /* we want to remap on the direct object, so we must resolve it first */ resolveFirst = DirectObject ; VerbRule(UseWith) 'use' singleDobj ('on' | 'with') singleIobj: UseWithAction verbPhrase = 'use/using (what) (on what)' ; VerbRule(UseWithWhat) 'use' singleDobj: UseWithAction verbPhrase = 'use/using (what) (on what)' construct() { /* set up the empty indirect object phrase */ iobjMatch = new EmptyNounPhraseProd(); iobjMatch.responseProd = withSingleNoun; } ; modify Thing dobjFor(UseWith) { verify() { } } iobjFor(UseWith) { verify() { illogical('Please be more specific. '); }} ; /* * USE WITH maps to LOCK/UNLOCK WITH */ modify Key dobjFor(UseWith) { verify() { verifyIobjLockWith(); } preCond() { return preCondIobjLockWith(); } remap() { /* * If we have a single tentative indirect object, check to * see if it's locked or unlocked, and use the appropriate * verb (LOCK or UNLOCK) to reverse its state. If we can't * yet identify the indirect object, guess that it's UNLOCK. */ if (gTentativeIobj.length() == 1) { /* * If the object is locked, unlock it; otherwise lock * it. Note that we must reverse the dobj/iobj roles in * the remapped verb, because the key is the direct * object of USE but the indirect object of LOCK/UNLOCK. */ if (gTentativeIobj[1].obj_.isLocked) return [UnlockWithAction, OtherObject, self]; else return [LockWithAction, OtherObject, self]; } else { /* guess that it's UNLOCK */ return [UnlockWithAction, OtherObject, self]; } } } ; /* ------------------------------------------------------------------------ */ /* * "fill x with y" - treat this as "put y in x". This verb isn't really * useful in this sample game; it's here only as a demonstration of * using the TIAction remapping macros. */ DefineTIAction(FillWith) resolveFirst = DirectObject ; VerbRule(FillWith) 'fill' singleDobj 'with' iobjList : FillWithAction verbPhrase = 'fill/filling (what) (with what)' ; modify Thing dobjFor(FillWith) remapTo(PutIn, IndirectObject, self); ; /* ------------------------------------------------------------------------ */ /* * Main startup object. This lets us customize the introductory text, * set the player character object, and control the game's startup * procedure. */ gameMain: GameMainDef /* the initial (and only) player character is 'me' */ initialPlayerChar = me /* show our introduction message */ showIntro() { /* show our simple introduction message */ "Welcome to the TADS 3 Library Sample Game!\b"; } /* * Create an "about box". * * The tag has to be re-displayed each time we clear the * screen, since the tag is part of the main text window's output * stream and thus only lasts as long as the rest of the text on the * main game screen. Fortunately, the library will take care of this * for us automatically, as long as we do two things. First, we have * to put our tag here, in this method, so that the * library knows how to re-display the tag when necessary. Second, * we must always use the cls() function (NOT the low-level * clearScreen() function) whenever we want to clear the game window. */ setAboutBox() { "
T3 Library Sampler

This is the T3 Library Sample Game. It's just an example of how to create a game using the T3 standard library (also known as adv3).
"; } /* show our end-of-game message */ showGoodbye() { "<.p>Thanks for playing the Library Sample Game!\b"; } ; /* ------------------------------------------------------------------------ */ /* * random test verbs */ VerbRule(rtfuse) 'test-rtfuse' : IAction execAction() { "Starting a real-time fuse for 10 seconds... "; new RealTimeFuse(testFuse, &execEvent, 10000); } ; VerbRule(rtdaemon) 'test-rtdaemon' : IAction execAction() { "Starting a real-time daemon for 10 second... "; new RealTimeDaemon(testDaemon, &execEvent, 10000); } ; VerbRule(moreprompt) 'moreprompt' : IAction execAction() { "Pausing for a MORE prompt, leaving the real-time clock running.\n"; inputManager.pauseForMore(nil); } ; VerbRule(morefreeze) 'morefreeze' : IAction execAction() { "Pausing for a MORE prompt, freezing the real-time clock.\n"; inputManager.pauseForMore(true); } ; VerbRule(cls) 'cls' : IAction execAction() { "Clearing the screen..."; cls(); } ; VerbRule(dialog) 'dialog' : IAction execAction() { local btn = inputManager.getInputDialog( InDlgIconQuestion, 'What did you have for breakfast this morning?', ['&Eggs', '&Cereal', '&Fruit', '&Other'], 1, 4); "You selected button <>! "; } ; VerbRule(dialog2) 'ldialog' : IAction execAction() { local btn = inputManager.getInputDialog( InDlgIconQuestion, 'Here\'s a much taller dialog. This one will go on for several lines, to make sure that the layout looks right with long text contents as well as a short one-liner. \n\n And this just goes on and on and on, doesn\'t it? Well, that\'s the whole idea. This dialog also uses standard buttons instead of the explicitly labeled buttons of the dialog you get with the "dialog" command, to test that the standard button setup is working properly. Well, that should probably be long enough!!!', InDlgOkCancel, 1, 2); "You selected button <>! "; } ; VerbRule(fuse) 'test-fuse' : IAction execAction() { "Starting a fuse for three turns... "; new Fuse(testFuse, &execEvent, 3); } ; testFuse: object execEvent() { "<.commandsep>This is the test fuse!<.commandsep>"; } ; VerbRule(daemon) 'test-daemon': IAction execAction() { "Starting daemon that will run three times, every other turn... "; new Daemon(testDaemon, &execEvent, 2); testDaemon.counter = 0; } ; VerbRule(getkey) 'getkey': IAction execAction() { local key; /* get a key */ key = inputManager.getKey(true, {: "<.p>Enter a key:\ " }); /* show the key */ "\nThanks! You typed '<>'!\n"; nestedActorAction(bob, Take, bigRedBall); } ; VerbRule(showlinks) 'showlinks': IAction execAction() { "Here are some links:
  • color << aHref('outside', 'outside')>> the link
  • color << aHref('inside', 'inside')>> the link
  • a mix of << aHref('mix', 'outside and inside the link') >> for extra completeness
"; } ; testDaemon: object execEvent() { "\bThis is the test daemon! "; #if 0 "<.p>*** You have died ***\n"; finishGame([finishOptionUndo, finishOptionCredits, finishOptionFullScore]); #endif /* if I've run three times now, cancel myself */ if (++counter == 3) eventManager.removeCurrentEvent(); } counter = 0 ; VerbRule(nbspTest) 'nbsptest' : IAction execAction() { "(This test is designed for 80-column displays.) \b This is a long line intended to wrap after a bit. The trick is that here to here (i.e., the parts between the first and second 'here') should be non-breaking: 'here to here' should all be on one line, even though there are spaces between the words. Anyway, so much for the test. "; } ; VerbRule(wrapTest) 'wraptest' : IAction execAction() { "(This test is designed for 80-column displays.) \b This is a long line intended to wrap after.\u2002First, let's test soft hy­phen­ation. Okay, we should have hyphenated somewhere in there. \b Next, let us make sure that regular hyphen wrapping still works. What we need is for this line to wrap after 'need'.\n Next, let us make sure that regular hyphen wrapping still works. What we need -- and this is key -- is to make sure this line wraps before 'need'.\n Next, let us make sure that regular hyphens still work. This time use hard-hyphen-wrapping, where we break at a hyphen in the middle of a word. \b Now we can test some new stuff. First, test the explicit break flag: We\u200bCan\u200bBreak\u200bAt\u200bAny\u200bOf\u200bThese"; "\u200bInter\u200bCaps\u200bCharacters\u200bAnd\u200bWe\u200bCan\u200bGo"; "\u200bOn\u200bLike\u200bThis\u200bFor\u200bQuite\u200bSome\u200bTime"; "\u200bEven\u200bTo\u200bThe\u200bExtent\u200bOf\u200bWrapping\u200bAnother"; "\u200bLine\u200bOf\u200bText! \b Next, let's test that explicit non-break points are working. We'll want to\ufeff \ufeffbreak just before the previous word 'break', but we have a non-breaking zero-width space after the 'to' and another before the 'break', which should prevent breaking there.\n Likewise, we'll want to break this line at a hyphen, coming up right here-\ufeffabsolutely. But we have a non-break right after the hyphen, which should tell us not to break there.\n For comparison, this line we do want to break at the hyphen, which is here-absolutely once again. This time we should break after the hyphen. \b The next test is for East Asian language wrapping.WeAreNowIn"; "CharacterWrapMode,WhichMeansThatWeCanWrapBetweenAnyTwoCharactersExcept"; "WhereOtherwiseNotedWithAnExplicitNonBreakingIndicator\ufeff.This is back in English-style text, which means we should wrap on word boundaries.AndNowWe'reBackInCharacterWrappingMode.TheTrickIsThat"; "WeWantToCheckTransitionsBetweenTheTwoModes.Such as here, where we are back in word mode.OrHereWithCharInTheMiddleOfALine."; "We're back in word-wrap mode here.\u2002The remaining test is to put word-wrap mode in the middle of a line ofCharacterWrapped"; "Text,SuchAsThis(here is some word-wrapped text)AndNow"; "WeAreBackToCharacterWrappedTextToTheEndOfTheLine.ATestWeHaveYetToTryWith"; "CharWrappedTextIs\ufeff.\ufeff.\ufeff.ExplicitNonBreakingIndicators. (That ellipsis should be all together, not broken across lines, and should furthermore stick to the letter just before it, so the ellipsis doesn't start a new line.)\n \b This is just a test of underlining some typographical spaces: let's try an en space:\u2002an em space:\u2003a thin space:\u2009a punctuation space:\u2008a figure space:\u2007a hair space:\u200a...and that should do it.\n And now let's try something similar:\u2002 ending the line with an en plus an em:\u2002\u2003This is to demonstrate that we trim trailing whitespace, even if the whitespace consists of typographical spaces.\n Another similar test:\u2002This time, some non-breaking spaces:\uFEFF\u2002\uFEFF\u2003\uFEFF\n That ended with an en plus em again, but these were quoted with FEFF's. \n \b

ThisIsSomeTextInATableInCharacterWrapMode.TheIdeaIsTo"; "SeeHowTablesDealWithThisSortOfThing.TablesUseTheBasicWrappingMechanism"; "LikeEverythingElse,ButHaveSomeSpecialCodeToMeasureTheSizeOfTheTextThat"; "WillGoInThem. This is a second column that uses character-based wrapping instead of word-based wrapping. The two columns should more or less balance out, although the word-based column will want a bigger share because of its larger minimum width.
\n"; } ; VerbRule(EventTest) 'inputevent' : IAction execAction() { local evt = inputManager.getEvent(true, {: "Waiting for event..." }); " Got it!\n"; local name = [InEvtKey -> 'key', InEvtHref -> 'href', InEvtEof -> 'eof'][evt[1]]; "Event type=<> (<>), param=<< evt.length() > 1 ? evt[2] : ''>> "; } ; VerbRule(logAbout) 'logabout' : IAction execAction() { "Logging the ABOUT command to About.txt...\n"; LogConsole.captureToFile('About.txt', getLocalCharSet(CharsetDisplay), 80, {: AboutAction.execSystemAction() }); } ; /* ------------------------------------------------------------------------ */ /* * A rather contrived preparser. * * This preparser allows the player to set "aliases" using syntax like * this: * * alias $xxx yyy * * where xxx is the name of an alias variable, and yyy is the * replacement text for the variable. Once an alias has been created, * it can be used in any command simply by naming it with the '$ prefix. */ StringPreParser /* pre-compile our search patterns */ patDefAlias = static new RexPattern( '*alias+(+)+(.+)') patUseAlias = static new RexPattern( '(<^dollar>*)(+)(.*)') doParsing(str, which) { local ret; local aliasName; local aliasText; /* check for the alias definition syntax */ if (rexMatch(patDefAlias, str) != nil) { /* extract the alias name and the replacement text */ aliasName = rexGroup(1)[3]; aliasText = rexGroup(2)[3]; /* add the alias to our table */ aliasTable[aliasName] = aliasText; /* mention that the alias has been defined */ "Okay, $<> has been defined. "; /* this command has been fully handled */ return nil; } /* we're not defining an alias, so look for aliases to replace */ ret = nil; for (;;) { /* look for a '$' in the balance of the input string */ if (rexMatch(patUseAlias, str) != nil) { local prv; local nxt; /* * extract the text before the alias, the name of the * alias, and the rest of the text after the alias */ prv = rexGroup(1)[3]; aliasName = rexGroup(2)[3]; nxt = rexGroup(3)[3]; /* add the previous text to the output string so far */ if (ret != nil) ret += prv; else ret = prv; /* * translate the alias; if it has no translation, then * use the original text of the alias, including the * '$', unchanged */ aliasText = aliasTable[aliasName]; if (aliasText == nil) aliasText = '$' + aliasName; /* add the translated alias to the output string so far */ ret += aliasText; /* continue parsing the balance of the input string */ str = nxt; } else { /* * We have no more aliases in the string - the balance * of the string is simply to be returned unchanged. If * we've already built some output text, add the balance * of the input to the output so far; otherwise, the * balance of the input is the entirety of the output. */ if (ret != nil) ret += str; else ret = str; /* we're done with the text */ break; } } /* return the output string */ return ret; } /* our lookup table of defined aliases */ aliasTable = static new LookupTable() ;