cfad47cfa3/t3compiler/tads3/samples/sample.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/*
4
 *   Sample game using the TADS 3 library.
5
 *   
6
 *   This is an extremely contrived game, and is not intended to be the
7
 *   least bit interesting to a player but rather serves as a test of
8
 *   various library features during library development.  
9
 */
10
11
#include "adv3.h"
12
#include "tok.h"
13
#include "en_us.h"
14
15
/*
16
 *   This is how we'd change a library grammar definition for a verb, if
17
 *   we wanted to replace library grammar with our own.  This doesn't
18
 *   change the rest of the VerbRule definition; it merely changes the
19
 *   grammar.  If we wanted to change the rest of the VerbRule, we'd use
20
 *   'replace' rather than 'modify'.  Note that 'replace' and' modify'
21
 *   both replace the grammar rule itself, though; they differ in how they
22
 *   handle the *rest* of the properties of the VerbRule object.
23
 *   
24
 *   To delete a library verb rule grammar, modify the rule and use an
25
 *   unmatchable token as the replacement grammar rule (' ' will work,
26
 *   since the standard tokenizer won't return a space character as a
27
 *   token).  
28
 */
29
//modify VerbRule(Examine)
30
//    ('ex' | 'lkat') DobjList :
31
//;
32
33
34
/* ------------------------------------------------------------------------ */
35
/*
36
 *   Game credits and version information
37
 */
38
versionInfo: GameID
39
    IFID = 'fa555579-c107-f533-d2a8-d2c93ca87ea0'
40
    name = 'TADS 3 Library Sampler'
41
    version = '1.0'
42
    byline = 'by M.J.Roberts'
43
    htmlByline =
44
        'by <a href="mailto:mjr_@hotmail.com">M.&nbsp;J.&nbsp;Roberts</a>'
45
    authorEmail = 'M.J. Roberts <mjr_@hotmail.com>'
46
    desc = 'A random sample and test of features of
47
               the tads 3 library.'
48
    htmlDesc = 'A random sample and test of features of the
49
                <b><font size=-1>TADS</font> 3 library</b>.'
50
51
    /* add some special text of our own for the credits */
52
    showCredit()
53
    {
54
        "<b><<name>></b>\n
55
        <<htmlByline>>\b
56
        Thanks to everyone who has participated in the TADS 3 library
57
        design process for contributing so many great ideas.
58
        \b
59
        <center>
60
        * * *
61
        \n
62
        </center>
63
        \b";
64
    }
65
66
    /* show "about" information */
67
    showAbout()
68
    {
69
        "This is just a boring sample game to test features of
70
        the library under construction.  Everything in this game
71
        is contrived to exercise a particular set of library
72
        features, and we make no attempt to make a coherent
73
        game out of this hodge-podge of tests.  A more interesting
74
        demonstration game will have to wait until the library
75
        is further along. ";
76
    }
77
;
78
79
/* ------------------------------------------------------------------------ */
80
/*
81
 *   Set up a barrier object so that we can conveniently prevent the
82
 *   tricycle from going past certain points.  This is just a generic
83
 *   vehicle barrier, but we customize the failure message.  
84
 */
85
tricycleBarrier: VehicleBarrier
86
    construct(msg) { msg_ = msg; }
87
    explainTravelBarrier(traveler)
88
    {
89
        if (traveler == tricycle)
90
            reportFailure(msg_);
91
        else
92
            inherited(traveler);
93
    }
94
    msg_ = '{You/he}\'d best stay indoors with the tricycle; it would
95
            probably break if you rode it outside. '
96
; 
97
98
/* set up a barrier against pushing the television out of the house */
99
tvBarrier: PushTravelBarrier
100
    construct(msg) { msg_ = msg; }
101
    canPushedObjectPass(obj) { return obj != television; }
102
    explainTravelBarrier(traveler) { reportFailure(msg_); }
103
    msg_ = 'The television\'s casters are too small to
104
            allow the TV to go beyond this point. '
105
;
106
107
/* ------------------------------------------------------------------------ */
108
/*
109
 *   Trivial class for rooms in the house.  We use this mostly for
110
 *   identification of house rooms.  
111
 */
112
class HouseRoom: Room
113
;
114
115
class DarkHouseRoom: DarkRoom, HouseRoom
116
;
117
118
/* ------------------------------------------------------------------------ */
119
/*
120
 *   Basic Coin class 
121
 */
122
class Coin: Thing
123
    vocabWords = 'coin*coins'
124
    listWith = [coinGroup]
125
126
    /* 
127
     *   The base name for the coin as it appears in groups.  We leave off
128
     *   the word "coin" because the coin group prefix uses it.  
129
     */
130
    coinGroupBaseName = ''
131
132
    /* use the coinCollective for selected actions on coins */
133
    collectiveGroups = [coinCollective]
134
135
    /* 
136
     *   coin group name and counted name - we synthesize these from the
137
     *   group base name 
138
     */
139
    coinGroupName = ('one ' + coinGroupBaseName)
140
    countedCoinGroupName(cnt)
141
        { return spellIntBelow(cnt, 100) + ' ' + coinGroupBaseName; }
142
;
143
144
/*
145
 *   copper coins 
146
 */
147
class CopperCoin: Coin 'copper -' 'copper coin' @livingRoom
148
    isEquivalent = true
149
    coinValue = 1
150
    coinGroupBaseName = 'copper'
151
;
152
153
/* 
154
 *   silver coins 
155
 */
156
class SilverCoin: Coin 'silver -' 'silver coin' @livingRoom
157
    isEquivalent = true
158
    coinValue = 5
159
    coinGroupBaseName = 'silver'
160
;
161
162
/*
163
 *   gold coins 
164
 */
165
class GoldCoin: Coin 'gold -' 'gold coin' @livingRoom
166
    isEquivalent = true
167
    coinValue = 10
168
    coinGroupBaseName = 'gold'
169
170
    /* flag: I've awarded points for being taken by the player character */
171
    takeAwarded = nil
172
173
    /* award points when the item is first taken */
174
    afterAction()
175
    {
176
        /* inherit the default handling */
177
        inherited();
178
179
        /* 
180
         *   if I'm now being held directly or indirectly, and I haven't
181
         *   awarded my points for being taken before, award points now 
182
         */
183
        if (!takeAwarded && isIn(libGlobal.playerChar))
184
        {
185
            /* award my points */
186
            goldCoinAchievement.awardPoints();
187
188
            /* only award this achievement once per coin */
189
            takeAwarded = true;
190
        }
191
    }
192
;
193
194
/*
195
 *   Coin group - this is a listing group we use to group coins in room
196
 *   contents lists, object contents lists, and inventory lists.  
197
 */
198
coinGroup: ListGroupParen
199
    showGroupCountName(lst)
200
    {
201
        "<<spellIntBelowExt(lst.length(), 100, 0,
202
           DigitFormatGroupSep)>> coins";
203
    }
204
205
    /* 
206
     *   Just for the heck of it, order coins in a group in descending
207
     *   order of monetary value.  Since we want descending order, return
208
     *   the negative of the value comparison.  
209
     */
210
    compareGroupItems(a, b) { return b.coinValue - a.coinValue; }
211
212
    /*
213
     *   Customize the display of the individual coins listed in a coin
214
     *   group, so that we leave off the word "coin" from the name.  This
215
     *   will make our group list look like so:
216
     *   
217
     *   five coins (two gold, two silver, one copper) 
218
     */
219
    showGroupItem(lister, obj, options, pov, info)
220
        { say(obj.coinGroupName); }
221
    showGroupItemCounted(lister, lst, options, pov, infoTab)
222
        { say(lst[1].countedCoinGroupName(lst.length())); }
223
;
224
225
/*
226
 *   Coin Collective - this is a collective group object that we use to
227
 *   perform certain actions on coins collectively, rather than iteratively
228
 *   on the individuals.  This is an "itemizing" collective group, because
229
 *   we want "examine coins" to show a message listing the individual
230
 *   coins.  
231
 */
232
coinCollective: ItemizingCollectiveGroup '*coins' 'coins'
233
;
234
235
236
/* ------------------------------------------------------------------------ */
237
/*
238
 *   Balloons 
239
 */
240
class Balloon: Thing 'inflated uninflated popped balloon'
241
    /* balloons are by default equivalent to one another */
242
    isEquivalent = true
243
244
    /* list balloons in one place as a group */
245
    listWith = [balloonGroup]
246
247
    /* our state index - this is an index into our allStates list */
248
    state = nil
249
250
    /* get our current state - translates our index to a state */
251
    getState = (allStates[state])
252
253
    /* our list of all of our possible states */
254
    allStates = [balloonStateInflated,
255
                 balloonStateUninflated,
256
                 balloonStatePopped]
257
;
258
259
class RedBalloon: Balloon 'red -' 'red balloon';
260
class BlueBalloon: Balloon 'blue -' 'blue balloon';
261
class GreenBalloon: Balloon 'green -' 'green balloon';
262
263
/* use a simple unadorned grouper to keep all the balloons together */
264
balloonGroup: ListGroupSorted;
265
266
/* balloon state objects */
267
balloonStateInflated: ThingState 'inflated' +1
268
    stateTokens = ['inflated']
269
;
270
balloonStateUninflated: ThingState 'uninflated' +2
271
    stateTokens = ['uninflated']
272
;
273
balloonStatePopped: ThingState 'popped' +3
274
    stateTokens = ['popped']
275
;
276
277
278
sun: MultiFaceted
279
    initialLocationClass = OutdoorRoom
280
    instanceObject: Distant { 'sun' 'sun'
281
        "It's the yellow star around which the Earth orbits. "
282
    }
283
;
284
285
286
/* ------------------------------------------------------------------------ */
287
/*
288
 *   Living Room 
289
 */
290
291
redBook: Thing 'red book*books' 'red book' @livingRoom;
292
blueBook: Thing 'blue test book/booklet*books booklets'
293
    'blue test booklet' @livingRoom;
294
bigRedBall: Thing 'big large red ball*balls' 'large red ball' @livingRoom;
295
smallRedBall: Thing 'small little red ball*balls' 'small red ball' @livingRoom;
296
greenBall: Thing 'green ball*balls' 'green ball' @livingRoom;
297
box: Container 'brown cardboard box' 'brown box' @livingRoom;
298
poBox: Thing 'p. o. p.o. po post office # box' 'PO Box' @livingRoom;
299
eightball: Thing '8 ball/8-ball*balls' '8-ball' @livingRoom;
300
301
greenJar: OpenableContainer 'green glass jar*jars' 'green jar' @livingRoom
302
    "It's made of transparent green glass. "
303
    material = glass
304
    initiallyOpen = nil
305
;
306
307
clearJar: OpenableContainer 'clear glass jar*jars' 'clear jar' @livingRoom
308
    "It's made of clear glass. "
309
    material = glass
310
    initiallyOpen = true
311
    isEquivalent = true
312
;
313
314
CopperCoin location=greenJar;
315
CopperCoin location=greenJar;
316
317
SilverCoin;
318
SilverCoin;
319
SilverCoin;
320
321
GoldCoin;
322
GoldCoin;
323
GoldCoin;
324
GoldCoin;
325
GoldCoin;
326
327
/* achievement for obtaining a gold coin */
328
goldCoinAchievement: Achievement
329
    "obtaining <<spellInt(scoreCount)>> gold coin<<
330
      scoreCount > 1 ? 's' : ''>>"
331
332
    /* 
333
     *   we're worth two points, and we can be awarded up to five times
334
     *   (one time per gold coin), for a total of ten points 
335
     */
336
    points = 2
337
    maxPoints = 10
338
;
339
340
ironKey: Key 'iron key*keys' 'iron key' @livingRoom
341
    "It's an ordinary iron key. "
342
;
343
brassKey: Key 'brass key*keys' 'brass key' @livingRoom
344
    "It's an ordinary brass key. "
345
;
346
rustyKey: Key 'rusty key*keys' 'rusty key' @livingRoom
347
    "It's an ordinary rusty key. "
348
;
349
350
/*
351
 *   Note that we don't have to define any vocabulary for the player
352
 *   character, since the library automatically defines the appropriate
353
 *   pronouns for any actor being used as the player character.  The
354
 *   pronoun selection is automatic and dynamic, so if we change to
355
 *   another player character later, the pronouns will all switch
356
 *   automatically at the same time.  
357
 */
358
me: Person
359
    location = livingRoom
360
    bulkCapacity = 5 // to force lots of bag-of-holding shuffling, for testing
361
//  referralPerson = firstPerson
362
363
    /* 
364
     *   When we issue a series of commands to another actor, we can wait
365
     *   until the entire series finishes before we take another turn.
366
     *   Set this flag to true to wait, or nil to allow us to take turns
367
     *   while the other actor carries out our orders. 
368
     */
369
    issueCommandsSynchronously = true
370
;
371
372
+ duffel: BagOfHolding, Openable, Container 'duffel bag' 'duffel bag'
373
    "It's a really capacious bag. "
374
;
375
376
+ keyring: Keyring 'key ring/keyring' 'keyring'
377
;
378
379
bob: Person 'bob' 'Bob' @livingRoom
380
    isProperName = true
381
    isHim = true
382
383
    /* obey any command from any other character */
384
    obeyCommand(issuer, action) { return true; }
385
386
    /* 
387
     *   We want to be able to follow other actors if requested, so keep
388
     *   track of departure data.  NPC's don't track departure information
389
     *   by default, because most NPC's never need it, and tracking it
390
     *   consumes a little extra time and memory.  
391
     */
392
    wantsFollowInfo(obj) { return true; }
393
;
394
395
/*
396
 *   Respond to any ASK ABOUT topic with a generic message incorporating
397
 *   the tokens from the topic.  
398
 */
399
+ DefaultAskTopic
400
    "<q>Ah, yes, <<gTopic.getTopicText()>>, very interesting...</q> "
401
;
402
403
+ GiveTopic
404
    matchTopic(fromActor, obj)
405
    {
406
        /* we match any coin */
407
        return obj.ofKind(Coin) ? matchScore : nil;
408
    }
409
410
    handleTopic(fromActor, obj)
411
    {
412
        /* accept the coin into my actor's inventory */
413
        obj.moveInto(getActor());
414
415
        /* add our special report */
416
        gTranscript.addReport(new AcceptCoinReport(obj));
417
418
        /* register for collective handling at the end of the command */
419
        gAction.callAfterActionMain(self);
420
    }
421
422
    afterActionMain()
423
    {
424
        /*
425
         *   adjust the transcript by summarizing consecutive coin
426
         *   acceptance reports 
427
         */
428
        gTranscript.summarizeAction(
429
            {x: x.ofKind(AcceptCoinReport)},
430
            {vec: '\^' + getActor().theName + ' accepts the '
431
                  + spellInt(vec.length()) + ' coins. ' });
432
    }
433
;
434
435
+ AskForTopic [ironKey, brassKey, rustyKey]
436
    handleTopic(fromActor, topic)
437
    {
438
        "<q>So, you want a key, do you? Let's see... ";
439
        SimpleLister.showSimpleList(topic.inScopeList + topic.likelyList);
440
        "...</q>";
441
    }
442
;
443
444
class AcceptCoinReport: MainCommandReport
445
    construct(obj)
446
    {
447
        /* remember the coin we accepted */
448
        coinObj = obj;
449
450
        /* inherit the default handling */
451
        gMessageParams(obj);
452
        inherited('Bob accepts {the obj/him}. ');
453
    }
454
455
    /* my coin object */
456
    coinObj = nil
457
;
458
459
bill: Person 'bill' 'Bill' @livingRoom
460
    isProperName = true
461
    isHim = true
462
    obeyCommand(issuer, action) { return true; }
463
    wantsFollowInfo(obj) { return true; }
464
465
    /* 
466
     *   Make Bill go after Bob.  This normally wouldn't be important,
467
     *   since the relative order of actor turns is usually arbitrary, but
468
     *   we want our test scripts to be stable so we establish a fixed
469
     *   order.  We accomplish the order adjustment simply by giving Bill a
470
     *   slightly higher schedule order than the default.  
471
     */
472
    calcScheduleOrder()
473
    {
474
        inherited();
475
        scheduleOrder += 1;
476
    }
477
;
478
479
livingRoom: HouseRoom
480
    roomName = 'Living Room'
481
    destName = 'the living room'
482
    desc = "It's a nice, big room.  A potted plant is the only attempt
483
            at decoration.  Passages lead north, east, and west.
484
            To the south, the front door of the house
485
            leads outside. "
486
    north = diningRoom
487
    east = den
488
    south = frontDoor
489
    out asExit(south)
490
    west = platformRoom
491
;
492
493
+ tricycle: Vehicle, BasicChair 'tricycle/trike' 'tricycle'
494
    "It's a child's three-wheeled bike. "
495
496
    gaveInstructions = nil
497
498
    allowedPostures = [sitting]
499
500
    /*
501
     *   Customize the travel arrival/departure messages for certain types
502
     *   of connectors.  The default message is of the form "The tricycle
503
     *   (carrying whoever) arrives from the east."  This isn't great for
504
     *   the tricycle, which only carries one rider, and which only goes
505
     *   when the rider makes it go; a better message would be "Bob rides
506
     *   the tricycle in from the east," which better captures that the
507
     *   rider is the one making the tricycle go somewhere.  
508
     */
509
    sayArrivingDir(dir, conn)
510
    {
511
        local actor = getTravelerActors()[1];
512
        
513
        "<<actor.name>> ride<<actor.verbEndingS>> in from the
514
        <<dir.name>> on a tricycle. ";
515
    }
516
    sayArrivingThroughPassage(conn)
517
    {
518
        local actor = getTravelerActors()[1];
519
        
520
        "<<actor.name>> ride<<actor.verbEndingS>> in through
521
        <<conn.theName>> on a tricycle. ";
522
    }
523
    sayDepartingDir(dir, conn)
524
    {
525
        local actor = getTravelerActors()[1];
526
        
527
        "<<actor.name>> ride<<actor.verbEndingS>> the tricycle off
528
        to the <<dir.name>>. ";
529
    }
530
    sayDepartingThroughPassage(conn)
531
    {
532
        local actor = getTravelerActors()[1];
533
        
534
        "<<actor.name>> ride<<actor.verbEndingS>> off
535
        through <<conn.theName>> on the tricycle. ";
536
    }
537
538
    dobjFor(Ride) asDobjFor(SitOn)
539
540
    dobjFor(SitOn)
541
    {
542
        check()
543
        {
544
            /* don't allow riding the tricycle except in the house */
545
            if (!gActor.location.getOutermostRoom().ofKind(HouseRoom))
546
            {
547
                reportFailure('{You/he} shouldn\'t ride {the dobj/him}
548
                    except in the house; {it dobj/he} might break. ');
549
                exit;
550
            }
551
        }
552
553
        action()
554
        {
555
            /* inherit the default handling */
556
            inherited();
557
558
            /* if we haven't given instructions previously, do so now */
559
            if (!gaveInstructions)
560
            {
561
                mainReport('Okay, {you\'re} now on the tricycle.  {You/he}
562
                    can probably manage to ride around on it if
563
                    {it/he} tr{ies}; just say the direction you\'d like '
564
                    + (gActor.isPlayerChar ? '' : '{it/him}') + ' to go. ');
565
                gaveInstructions = true;
566
            }
567
        }
568
    }
569
570
    dobjFor(Eat) { verify() { } action() { "{You/he} eat{s} {your} tricycle. "; } }
571
;
572
573
+ frontDoor: Door 'front door' 'front door'
574
    "It leads outside to the south. "
575
    travelBarrier = [tricycleBarrier, tvBarrier]
576
;
577
578
+ Decoration
579
    'potted plant small green clay plant/tree/pot/leaf/leaves/dirt/soil'
580
    'potted plant'
581
    "It's a tree with small green leaves, about six feet tall and
582
    growing in a clay pot filled with soil. "
583
;
584
585
+ television: TravelPushable
586
    'old huge boxy model/unit/television/tv' 'television'
587
    "It's an old, huge, boxy model, certainly black-and-white.  The
588
    bulbous picture tube shows no sign of life.  Two dials control
589
    the channel selection (it looks like it's currently <<curChannel>>),
590
    but the channel setting hardly matters when the set doesn't work
591
    in the first place.  It seems to be on small casters, which is
592
    a good thing given how big it is and how heavy it looks. "
593
594
    specialDesc = "An old television, a huge, boxy, massive-looking thing,
595
                   sits on the floor. "
596
    specialNominalRoomPartLocation = defaultFloor
597
598
    curChannel()
599
    {
600
        if (upperDial.curSetting == 'UHF')
601
            return lowerDial.curSetting;
602
        else
603
            return upperDial.curSetting;
604
    }
605
;
606
607
++ Decoration 'casters' 'casters'
608
    "Small wheels, intended to make it easier to move the unit's
609
    huge bulk around the house. "
610
;
611
612
++ Decoration 'bulbous picture tube/screen' 'picture tube'
613
    "It shows no pictures. ";
614
615
++ upperDial: NumberedDial, Component
616
    'upper tv television channel dial*dials' 'upper dial'
617
    "This is the VHF selector dial; it can be set to a channel from 2
618
    through 13, or to the additional stop marked \"UHF\". It's
619
    currently set to <<curSetting>>. "
620
621
   minSetting = 2
622
   maxSetting = 13
623
   curSetting = 8
624
   isValidSetting(val)
625
   {
626
       /* allow the special 'uhf' setting in addition to the numbers */
627
       if (val.toLower() == 'uhf')
628
           return true;
629
630
       /* inherit the default handling to check the numbered settings */
631
       return inherited(val);
632
   }
633
   makeSetting(val)
634
   {
635
       /* if the setting is 'uhf', change it to all caps */
636
       if (val.toLower() == 'uhf')
637
           val = 'UHF';
638
639
       /* inherit default handling with the (possibly) adjusted value */
640
       inherited(val);
641
   }
642
;
643
644
++ lowerDial: NumberedDial, Component
645
    'lower tv television channel dial*dials' 'lower dial'
646
    "This is the UHF selector dial; it can be set to a channel from
647
    14 through 82.  It's currently set to <<curSetting>>.  You probably
648
    have to turn the upper dial to \"UHF\" for this dial's setting
649
    to have any effect. "
650
    minSetting = 14
651
    maxSetting = 82
652
    curSetting = 44
653
;
654
655
656
+ Consultable 'phone book' 'phone book'
657
    "You can probably look up people you know in here. "
658
;
659
++ ConsultTopic @bob "Bob's number is (415)555-1212. ";
660
++ ConsultTopic @bill "Bill's number is (650)555-1212. ";
661
++ ConsultTopic @salesman "Ron's number is (510)555-1212. ";
662
++ ConsultTopic @insCompany
663
    "<q>Actuarial Life Insurance Company!  Call for best rates!
664
    (800)555-1212!</q> "
665
;
666
++ DefaultConsultTopic "You can't find any such listing. ";
667
668
/* ------------------------------------------------------------------------ */
669
/*
670
 *   Platform room 
671
 */
672
platformRoom: HouseRoom 'Platform Room' 'the platform room'
673
    "This room is obviously highly contrived.  Filling one end of the
674
    room is a platform, almost like a theater stage, raised a couple of
675
    feet above the floor.  At opposite ends of the 
676
    platform are two smaller platforms, one red and one blue; 
677
    atop each smaller platform is a chair of the same color as its platform.
678
    In the center of the main platform is a raised dais, upon which
679
    <<stool.isDirectlyIn(dais) ? "are a wooden stool and" : "is">> a
680
    leather armchair.
681
    <.p>The only exit is east. "
682
683
    east = livingRoom
684
    down = (whiteBox.isOpen ? whiteBox.down : nil)
685
;
686
687
+ Fixture, Platform
688
    'main theater theatre platform/stage*platforms' 'main platform'
689
;
690
691
++whiteBox: Openable, Booth 'large white cardboard box' 'white box'
692
    useInitSpecialDesc()
693
    {
694
        /* show our initial description only when the actor isn't in me */
695
        return inherited() && !gActor.isIn(self);
696
    }
697
    initSpecialDesc = "On the main platform is a large white box,
698
        about waist high and quite roomy. "
699
700
    /* don't mention our contents if we're using our initial description */
701
    contentsListed = (!useSpecialDesc())
702
703
    interiorDesc = "The box is roomy, but not enough that you can
704
                    stand up in it when it's closed. "
705
706
    down = whiteBoxTrapDoor
707
708
    /* make it paper, so we can hear through the box */
709
    material = paper
710
711
    /* 
712
     *   we can't stand up in the box when it's closed, so make the
713
     *   default posture sitting 
714
     */
715
    defaultPosture = (isOpen ? standing : sitting)
716
717
    /* we can't close the box when someone's standing in it */
718
    makeOpen(stat)
719
    {
720
        /* if we're closing the box, check to make sure we're allowed to */
721
        if (!stat)
722
        {
723
            /* 
724
             *   We're trying to close the box; if anyone's standing in
725
             *   the box, don't allow it. 
726
             */
727
            foreach (local cur in allContents())
728
            {
729
                /* if this is a standing actor, disallow closure */
730
                if (cur.isActor && cur.posture == standing)
731
                {
732
                    /* 
733
                     *   we can't close - issue a failure report and
734
                     *   terminate the command 
735
                     */
736
                    reportFailure('{You/he} cannot close the box while
737
                        anyone is standing in it. ');
738
                    exit;
739
                }
740
            }
741
        }
742
743
        /* no problems - inherit default handling */
744
        inherited(stat);
745
    }
746
747
    /* 
748
     *   if they try to stand within a nested room within us, mention that
749
     *   they can only sit 
750
     */
751
    roomBeforeAction()
752
    {
753
        /* 
754
         *   If we're closed, and the action is 'stand', and they're
755
         *   something nested within me, mention after the fact that
756
         *   there's no room to stand up.  We need to mention this, but we
757
         *   do not need to enforce the constraint here because the nested
758
         *   room will set our default sitting posture when moving an
759
         *   actor from an interior location.  
760
         */
761
        if (!isOpen
762
            && gActionIs(Stand)
763
            && gActor.isIn(self)
764
            && !gActor.isDirectlyIn(self))
765
            reportAfter('There isn\'t room to stand up in here, so
766
                {you/he} sit{s} in the box. ');
767
    }
768
769
    /* enforce the low headroom when the box is closed */
770
    makeStandingUp()
771
    {
772
        if (isOpen)
773
        {
774
            /* we're open, so proceed as normal */
775
            inherited();
776
        }
777
        else
778
        {
779
            /* the box is closed, so they can't stand up */
780
            reportFailure('There\'s not enough room to stand up in
781
                the box while it\'s closed. ');
782
        }
783
    }
784
;
785
786
+++ whiteBoxTrapDoor: Fixture, Door -> tunnelTrapDoor
787
    'small trap door' 'trap door'
788
    "It's set into the floor of the box; it looks just large enough
789
    for you to fit through. "
790
791
    /* 
792
     *   list me *after* the room's contents, since the box is sometimes
793
     *   listed with the room's contents 
794
     */
795
    specialDescBeforeContents = nil
796
797
    initSpecialDesc = "A small trap door is set into the floor of the box. "
798
799
    /* don't require standing up before entering the trap door */
800
    actorTravelPreCond(actor) { return []; }
801
802
    enteredBefore = nil
803
    dobjFor(TravelVia)
804
    {
805
        action()
806
        {
807
            /* mention the ladder below */
808
            if (enteredBefore)
809
                "You find the ladder below and climb down. ";
810
            else
811
            {
812
                "You tentatively lower yourself through the door, and
813
                manage to find what feels like a ladder below, so you
814
                carefully climb down. ";
815
                enteredBefore = true;
816
            }
817
818
            /* inherit the default handling */
819
            inherited();
820
        }
821
    }
822
;
823
824
+++ Switch 'small black plastic beeper pager box/beeper/pager/switch' 'beeper'
825
    "It's a small black plastic box with a single red light
826
    (which is currently <<isOn ? 'lit' : 'off'>>) and a
827
    switch (currently <<isOn ? 'on' : 'off'>>). "
828
829
    /* 
830
     *   if they know about us, they'll recognize our beeping, so they'll
831
     *   want to be able to refer to the beeper itself; thus, give the
832
     *   beeper a sound presence once it's been seen 
833
     */
834
    soundPresence = (isOn && seen)
835
836
    isOn = true
837
    makeOn(val)
838
    {
839
        /* inherit the default handling */
840
        inherited(val);
841
842
        /* add/remove our beeping sound, as appropriate */
843
        beeperNoise.moveInto(val ? self : nil);
844
    }
845
;
846
847
++++ Component 'tiny small red beeper light' 'beeper light'
848
    "It's a tiny red light, currently <<location.isOn ? 'lit' : 'off'>>. "
849
;
850
851
++++ beeperNoise: Noise
852
    'piercing high-pitched high pitched beeping sound/noise/beep'
853
    'beeping sound'
854
    sourceDesc = "The beeper is making a piercing, high-pitched beeping
855
                  noise. "
856
    descWithSource = "The beeper is beeping, which is what they do. "
857
    descWithoutSource = "It's a piercing, high-pitched beeping noise. "
858
859
    hereWithSource = "The beeper is making a piercing beeping noise. "
860
    hereWithoutSource = "You can hear a high-pitched beeping noise. "
861
862
    displaySchedule = [2, 4]
863
;
864
865
++ Fixture, Platform 'small smaller red platform*platforms' 'red platform'
866
;
867
868
class PlatformChair: Chair
869
    dobjFor(SitOn)
870
    {
871
        verify()
872
        {
873
            /* inherit default */
874
            inherited();
875
876
            /* 
877
             *   if they're directly in my location, and my location is a
878
             *   nested room of some kind, boost likelihood that this is
879
             *   where they want to sit 
880
             */
881
            if (gActor.isDirectlyIn(location) && location.ofKind(NestedRoom))
882
                logicalRank(150, 'adjacent');
883
        }
884
    }
885
;
886
887
+++ Fixture, PlatformChair 'red chair' 'red chair'
888
;
889
890
++ Fixture, Platform 'small smaller blue platform*platforms' 'blue platform'
891
;
892
893
+++ Fixture, PlatformChair 'blue chair' 'blue chair'
894
;
895
896
++ dais: Fixture, Platform 'raised dais' 'dais'
897
;
898
899
+++ stool: PlatformChair 'wooden wood stool' 'stool'
900
    /* 
901
     *   since we're part of the main room description if we're in our
902
     *   original location, don't list me if I'm on the dais 
903
     */
904
    isListed = (location == dais ? nil : inherited)
905
;
906
907
+++ Fixture, PlatformChair 'leather arm chair/armchair' 'armchair'
908
    actorInPrep = 'in'
909
;
910
911
#if 0 // disabled by default
912
/* 
913
 *   enable this to test multi-location sense connections - this is a bit
914
 *   too contrived an example, so we disable it by default 
915
 */
916
whiteBoxHole: SenseConnector, Fixture 'small hole' 'small hole'
917
    "It's a small hole in the box. "
918
    locationList = [whiteBox, backYard]
919
    connectorMaterial = glass
920
;
921
#endif
922
923
924
/* ------------------------------------------------------------------------ */
925
/*
926
 *   Front yard 
927
 */
928
frontYard: OutdoorRoom 'Front Yard' 'the front yard'
929
    "This is a small yard in front of a modest house.  The front
930
    door to the house leads in to the north.  To the south is
931
    the street. "
932
    in asExit(north)
933
    north = frontDoorOutside
934
    south: NoTravelMessage { "You'd rather not venture beyond the
935
               immediate vicinity of the house right now. " }
936
    atmosphereList: RandomEventList {
937
        eventList = [
938
            nil,
939
            'A car drives past. ',
940
            'Someone honks a horn in the distance. ',
941
            nil,
942
            'A few birds pass overhead. ',
943
            'A couple of bicyclists pedal past on the street. ',
944
            nil,
945
            nil ]
946
    }
947
;
948
949
+ frontDoorOutside: Door ->frontDoor 'front door' 'front door'
950
    "It leads north, into the house. "
951
;
952
953
+ Enterable, Decoration 'narrow residential street/road' 'street'
954
    "It's a narrow residential street. "
955
    connector = (frontYard.south)
956
;
957
958
+ Enterable, Decoration ->frontDoorOutside 'house' 'house'
959
    "It's a modest house.  A door leads in to the north. "
960
;
961
962
+ RedBalloon state = 3;
963
+ RedBalloon state = 3;
964
+ RedBalloon state = 3;
965
+ RedBalloon state = 3;
966
+ RedBalloon state = 1;
967
+ RedBalloon state = 1;
968
+ RedBalloon state = 2;
969
970
+ BlueBalloon state = 2;
971
+ BlueBalloon state = 1;
972
+ BlueBalloon state = 1;
973
974
+ GreenBalloon state = 3;
975
+ GreenBalloon state = 3;
976
+ GreenBalloon state = 3;
977
+ GreenBalloon state = 3;
978
+ GreenBalloon state = 3;
979
980
+ salesman: Person 'salesman/man/ron' 'salesman'
981
    "He's a thin, youngish man in a garish suit that's rather comically
982
    oversized on him. "
983
    isHim = true
984
;
985
++ Wearable 'oversized garish suit' 'suit'
986
    "It's an oddly bright shade of blue, and is way too large for its
987
    present wearer. "
988
   wornBy = salesman
989
;
990
++ Thing 'huge black briefcase' 'briefcase'
991
    "It's a huge black briefcase. "
992
;
993
994
/* the salesman's initial state */
995
++ ActorState
996
    isInitState = true
997
    specialDesc = "A youngish man in a garish suit is standing in
998
                   the yard near the front door. "
999
1000
    takeTurn()
1001
    {
1002
        if (gPlayerChar.location == frontYard)
1003
            salesman.initiateConversation(salesConv, 'sales-hello');
1004
        inherited();
1005
    }
1006
;
1007
1008
++ salesConv: InConversationState
1009
    attentionSpan = 10000
1010
    nextState = salesWaiting
1011
    stateDesc = "He's smiling eagerly at you. "
1012
    specialDesc = "The salesman is standing a little too close,
1013
                  smiling a bit too much. "
1014
;
1015
1016
++ salesWaiting: ConversationReadyState
1017
    specialDesc = "The salesman is standing in the yard. "
1018
1019
    inConvState = salesConv
1020
1021
    takeTurn()
1022
    {
1023
        /* re-arm for conversation only when the PC is gone */
1024
        if (gPlayerChar.location != frontYard)
1025
            regreet = true;
1026
1027
        /* greet again randomly if we're re-armed and the PC is present */
1028
        if (regreet && gPlayerChar.location == frontYard && rand(100) < 33)
1029
            salesman.initiateConversation(salesConv, 'sales-1');
1030
1031
        inherited();
1032
    }
1033
1034
    /* flag: we're ready to re-greet the player */
1035
    regreet = nil
1036
1037
    /* on activation, set the re-greet flag to nil by default */
1038
    activateState(actor, oldState)
1039
    {
1040
        inherited(actor, oldState);
1041
        regreet = nil;
1042
    }
1043
;
1044
+++ HelloTopic
1045
    "You tap the salesman on the shoulder.  <q>Oh, hi!</q> he says.
1046
    <q>I\'d sure like to talk to you about life insurance.</q>
1047
    <.convnode sales-1> "
1048
;
1049
+++ ByeTopic, ShuffledEventList
1050
    ['<q>I have to go,</q> you say.
1051
    <.p><q>Thanks for your time!</q> the salesman says. ',
1052
    
1053
     '<q>Thanks, but not right now,</q> you say.
1054
     <.p><q>I\'ll be here when you\'re ready!</q> the salesman says. ']
1055
;
1056
+++ ImpByeTopic, ShuffledEventList
1057
    ['The salesman waves. <q>Okay, I\'ll wait here!</q> he says. ',
1058
     '<q>Thanks for your time!</q> the salesman says. ',
1059
     'The salesman calls after you, <q>I\'ll be here if you need me!</q> ']
1060
;
1061
1062
++ ConvNode 'sales-hello'
1063
    npcGreetingMsg = "<.p>The man in the bad suit walks up to you and
1064
                      extends his hand; by habit you shake his hand.
1065
                      <q>Hello, friend!  My name is Ron, and I represent the
1066
                      Acturial Life Insurance Company.</q> He releases
1067
                      your hand after a vigorous shaking. <q>You know, a lot
1068
                      of people I talk to don't know just how important life
1069
                      insurance is to proper financial planning.  Let me
1070
                      ask you this: do you have all the life insurance
1071
                      coverage you and your family need?</q> "
1072
1073
    npcContinueList: CyclicEventList {
1074
    [
1075
        'The salesman says eagerly, <q>Really, do you have enough
1076
        insurance?</q> ',
1077
        
1078
        'Ron says, <q>The sad fact is, most people <i>don\'t know</i>
1079
        how much insurance they really need.  I\'d really like to
1080
        tell you about our policies...</q><.convnode sales-1> '
1081
    ]}
1082
;
1083
+++ YesTopic
1084
    "<q>I'm covered,</q> you say.
1085
    <.p>Ron smiles and nods. <q>You know, a lot of people <i>think</i>
1086
    they're covered. But have you really <i>read</i> your insurance
1087
    policy?  Most insurance policies have so many exclusions and
1088
    limitations that you just can't rely on them.  That's why
1089
    Acturial Life created a new kind of insurance policy.  Let me
1090
    tell you about it...</q><.convnode sales-1> "
1091
;
1092
+++ NoTopic
1093
    "<q>Why, no,</q> you say.
1094
    <.p>Ron smiles eagerly. <q>Well, then it's a good thing I was in
1095
    your neighborhood today! Let me tell you about our policies...</q>
1096
    <.convnode sales-1> "
1097
;
1098
1099
++ ConvNode 'sales-1'
1100
    npcGreetingMsg = "<.p>Ron walks up to you. <q>Hello again, friend!
1101
                      It would be my pleasure to help you with your
1102
                      insurance needs.</q> "
1103
1104
    npcContinueList: ShuffledEventList {
1105
    ['The salesman looks serious for a moment. <q>Most people don\'t know
1106
     this, but death is our number one killer,</q> he says earnestly.
1107
     He goes back to smiling. <q>Fortunately, death is covered under
1108
     our policy\'s loss-of-life section.</q> ',
1109
1110
     'Ron says, <q>You know, life insurance is a lot more interesting
1111
     than most people think.</q> ',
1112
1113
     'The salesman says, <q>I\'m really glad I was in the neighborhood
1114
     today.  Everyone needs more insurance than they think.</q> '
1115
    ]}
1116
;
1117
1118
++ AskTellTopic, StopEventList @insPolicy
1119
    [
1120
        '<q>Okay, tell me about your policy,</q> you say.
1121
        <.p><q>Our policy is the best in the business,</q> Ron says.
1122
        <q>For starters, it\'s guaranteed go pay, which most policies
1123
        aren\'t.  There\'s so much more I can tell you.</q> ',
1124
1125
        '<q>Tell me more about your policy,</q> you say.
1126
        <.p><q>I could go on all day,</q> the salesman says. '
1127
    ]
1128
;
1129
1130
++ AskTellTopic, StopEventList @insCompany
1131
    ['<q>I\'ve never heard of your company,</q> you say.
1132
     <.p><q>Most people haven\'t,</q> Ron says. <q>We\'re the best-kept
1133
     secret in the business.</q>',
1134
1135
     '<q>Why would you want to keep your company a secret?</q>
1136
     <.p><q>One word: savings.  We save on marketing expenses, and
1137
     pass the low cost on to you.</q> ',
1138
1139
     '<q>What else can you tell me about your company?</q>
1140
     <.p><q>I\'d rather tell you about the policy.</q> ']
1141
;
1142
1143
/* some topics for the salesman to talk about */
1144
insPolicy: Topic 'life insurance policy/policies';
1145
insCompany: Topic 'acturial life insurance company';
1146
1147
/* ------------------------------------------------------------------------ */
1148
/*
1149
 *   Den 
1150
 */
1151
den: HouseRoom
1152
    roomName = 'Den'
1153
    destName = 'the den'
1154
    desc = "This small room is dominated by a desk, a massive steel
1155
            edifice painted a drab gray, something that would be
1156
            more at home in a government office during the cold war.
1157
            Behind the desk, built in to the north wall, is a
1158
            bookcase.  A passage leads west. "
1159
    west = livingRoom
1160
    north = bookcasePassage
1161
    roomParts = static (inherited() - defaultNorthWall)
1162
;
1163
1164
+ Decoration 'north wall*walls' 'north wall'
1165
    "A floor-to ceiling bookcase<<
1166
      bookcasePassage.isOpen ? " (which has swung aside to expose
1167
      a passage to the north)" : "">>
1168
    is built into the wall. "
1169
;
1170
1171
+ bookcase: Fixture 'book case/bookcase' 'bookcase'
1172
    desc
1173
    {
1174
        "It's a floor-to-ceiling bookcase built in to the north
1175
        wall, behind the desk, filled with hundreds of dusty tomes. ";
1176
        if (bookcasePassage.isOpen)
1177
            "The bookcase is evidently a secret door, because it
1178
            has moved sideways to expose a passage to the north. ";
1179
    }
1180
;
1181
1182
+ bookcasePassage: BasicOpenable, HiddenDoor
1183
    'passage' 'passage' "It leads north. "
1184
    initSpecialDesc
1185
    {
1186
        if (isOpen)
1187
            "The bookcase has moved aside to reveal a passage
1188
            to the north. ";
1189
    }
1190
;
1191
1192
+ Decoration 'dusty book/books/tome/tomes' 'books'
1193
    "There must be hundreds of books, all with incomprehensible
1194
    titles and authors you've never heard of. "
1195
    isPlural = true
1196
    dobjFor(Read)
1197
    {
1198
        verify() { }
1199
        action()
1200
        {
1201
            "Much as you enjoy reading, you can't find any book
1202
            whose title even means anything to you, let alone
1203
            holds any interest. ";
1204
        }
1205
    }
1206
;
1207
1208
+ desk: Immovable, Surface
1209
    'massive big huge edifice drab gray grey steel metal desk' 'desk'
1210
    desc
1211
    {
1212
        "It's a big desk with a single drawer (currently
1213
        <<deskDrawer.openDesc>>).  A small button is set inconspicuously
1214
        into the edge of the desktop";
1215
        if (hiddenPanel.isOpen)
1216
            ", and in the side is a small compartment containing a lever";
1217
        ". ";
1218
    }
1219
1220
    dobjFor(Open) remapTo(Open, deskDrawer)
1221
    dobjFor(Close) remapTo(Close, deskDrawer)
1222
    dobjFor(LookIn) remapTo(LookIn, deskDrawer)
1223
    iobjFor(PutIn) remapTo(PutIn, DirectObject, deskDrawer)
1224
;
1225
1226
++ Thing 'bob\'s brand premium fish cleaner jar/cleaner'
1227
    'jar of Bob\'s Brand Premium Fish Cleaner'
1228
    "It presumably once held fish cleaner (whatever that is),
1229
    but it's just an empty jar now. "
1230
;
1231
1232
++penCup: Container 'pen cup' 'pen cup' 
1233
   "It's a uneven clay cup, currently employed as a pen holder. " 
1234
1235
    /* just for fun, show my contents out-of-line in listings I'm in */
1236
    contentsListedSeparately = true
1237
; 
1238
1239
class pen: Thing 'pen*pens' 'pen' 
1240
   "A simple Bic. " 
1241
   isEquivalent = true 
1242
; 
1243
1244
+++pen; 
1245
+++pen; 
1246
+++pen; 
1247
+++pen; 
1248
1249
++ deskDrawer: Component, OpenableContainer 'desk drawer' 'desk drawer'
1250
;
1251
1252
++ Immovable 'old-fashioned rotary phone/telephone/dial/receiver/handset'
1253
    'phone'
1254
    "It's an old-fashioned rotary phone, very sturdy-looking but
1255
    rather scuffed from long use. "
1256
1257
    dobjFor(Take)
1258
    {
1259
        verify() { }
1260
        check() { }
1261
        action()
1262
        {
1263
            "You pick up the handset, but there's nothing but static
1264
            on the line.  At least it stops ringing.  You put the
1265
            handset back, and the phone starts ringing again. ";
1266
        }
1267
    }
1268
1269
    dobjFor(Answer) asDobjFor(Take)
1270
;
1271
1272
+++ Noise 'ring/ringing' 'ringing/bell'
1273
    sourceDesc = "The phone is ringing loudly. "
1274
    descWithSource = "It's an actual mechanical bell, not the ubiquitous
1275
                      electronic warble of modern phones. "
1276
    hereWithSource()
1277
    {
1278
        switch (displayCount)
1279
        {
1280
        case 1:
1281
            "The phone is ringing. ";
1282
            break;
1283
1284
        default:
1285
            "The phone is still ringing. ";
1286
            break;
1287
        }
1288
    }
1289
1290
    displaySchedule = [2, 4, 8]
1291
;
1292
1293
1294
++ Button, Component 'small button' 'small button'
1295
    dobjFor(Push)
1296
    {
1297
        action()
1298
        {
1299
            /* open/close the hidden panel */
1300
            hiddenPanel.makeOpen(!hiddenPanel.isOpen);
1301
1302
            /* show the appropriate message */
1303
            if (hiddenPanel.isOpen)
1304
                "A previously hidden panel in the side of the desk opens,
1305
                revealing a small compartment containing a lever. ";
1306
            else
1307
                "The panel in the desk closes, hiding the lever.  Now
1308
                that the panel is closed, it's completely seamless -
1309
                you can't see any sign of it. ";
1310
        }
1311
    }
1312
;
1313
1314
++ hiddenPanel: BasicOpenable, Component, Container
1315
    'hidden panel/compartment' 'compartment'
1316
    "It's a small compartment, just large enough to contain the
1317
    lever it enclosed. "
1318
1319
    bulkCapacity = 0
1320
    initiallyOpen = nil
1321
1322
    /* the panel is only visible when it's open */
1323
    sightPresence = (self.isOpen)
1324
;
1325
1326
+++ SpringLever, Component 'lever' 'lever'
1327
    "It's mounted in a small compartment in the desk. "
1328
    dobjFor(Pull)
1329
    {
1330
        action()
1331
        {
1332
            /* open/close the secret door */
1333
            bookcasePassage.makeOpen(!bookcasePassage.isOpen);
1334
1335
            /* show the appropriate message */
1336
            if (bookcasePassage.isOpen)
1337
                "The lever is surprisingly heavy, but you manage to
1338
                pull it all the way out.  At the end of its travel,
1339
                something under the floor clicks, and the bookcase
1340
                behind the desk slides sideways far enough to open
1341
                a passage to the north.  As soon as you release the
1342
                lever, it springs back to its original position. ";
1343
            else
1344
                "You pull the lever out all the way.  Something
1345
                under the floor clicks, and the bookcase slides
1346
                sideways until it covers the passage, leaving
1347
                no trace of a doorway. ";
1348
        }
1349
    }
1350
;
1351
1352
++ typewriter: Immovable
1353
    'big old old-fashioned black manual typewriter' 'typewriter'
1354
    initSpecialDesc = "A big, old-fashioned manual typewriter is sitting
1355
        on the desk. "
1356
    desc = "It's big, black, manual typewriter, like something out
1357
        of an old black-and-white movie about newspapermen or private
1358
        detectives.  There's a piece of paper in it. "
1359
    dobjFor(TypeOn) { verify() { } }
1360
    dobjFor(TypeLiteralOn)
1361
    {
1362
        verify() { }
1363
        action()
1364
        {
1365
            /* add the literal text to the paper */
1366
            typewriterPaper.addText(gAction.getLiteral());
1367
            mainReport('With some effort, {you/he} work{s} the keys of the
1368
                ancient machine, noisily impressing <q>'
1369
                + gAction.text_ + '</q> on the paper. ');
1370
        }
1371
    }
1372
;
1373
1374
+++ typewriterPaper: Thing 'piece/paper' 'piece of paper'
1375
    isListedInContents = nil
1376
    desc
1377
    {
1378
        if (textList.length() == 0)
1379
            "The paper is blank. ";
1380
        else
1381
        {
1382
            "The typewritten letters on the page have that uneven
1383
            darkness and wandering alignment characteristic of
1384
            the work a well-worn manual typewriter:<tt>\b";
1385
1386
            foreach (local cur in textList)
1387
                "\t<<cur>>\n";
1388
1389
            "</tt>";
1390
        }
1391
    }
1392
    addText(txt) { textList.append(txt); }
1393
    textList = static new Vector(10)
1394
    moveInto(obj)
1395
    {
1396
        reportFailure('On second thought, {you/he}\'d rather leave the
1397
            paper in the typewriter in case someone needs to do
1398
            some typing. ');
1399
        exit;
1400
    }
1401
    dobjFor(TypeOn) { verify() { } }
1402
    dobjFor(TypeLiteralOn)
1403
    {
1404
        verify()
1405
        {
1406
            /* 
1407
             *   allow it but reduce the likelihood - we want the
1408
             *   typewriter to win in a default case 
1409
             */
1410
            logicalRank(50, 'nondefault');
1411
        }
1412
        action()
1413
        {
1414
            /* treat 'type on paper' as 'type on typewriter' */
1415
            replaceAction(TypeLiteralOn, typewriter, gAction.text_);
1416
        }
1417
    }
1418
;
1419
1420
++ dagger: Thing 'dagger' 'dagger'
1421
    initSpecialDesc = "Someone has jabbed a dagger into the top of
1422
        the desk, leaving the dagger sticking up almost vertically. "
1423
    initExamineDesc = "Its point is stuck into the top of the desk. "
1424
;
1425
1426
++ watch: Wearable 'watch' 'watch' "It has a minute hand and an hour hand. ";
1427
+++ Component 'minute big hand' 'minute hand';
1428
+++ Component 'hour little hand' 'hour hand';
1429
1430
++ Container 'glass bottle' 'bottle'
1431
    bulkCapacity = 2
1432
    canFitObjThruOpening(obj)
1433
    {
1434
        /* we can only fit small objects through the opening */
1435
        return obj.bulk < 2;
1436
    }
1437
;
1438
1439
+++ Thing 'minature model sailing ship/boat' 'model ship'
1440
    "It's a miniature model of a sailing ship. "
1441
    bulk = 2
1442
;
1443
1444
#if 0 // for testing distant viewing of sightSize=large objects
1445
viewScreen: SenseConnector, Fixture 'view screen/viewscreen' 'viewscreen'
1446
    "It's a viewscreen, displaying a close-up shot of a floating sphere. "
1447
    locationList = [den, backYard]
1448
    connectorMaterial: Material
1449
    {
1450
        seeThru = distant
1451
        hearThru = distant
1452
        smellThru = opaque
1453
        touchThru = opaque
1454
    }
1455
;
1456
#endif
1457
1458
/* ------------------------------------------------------------------------ */
1459
/*
1460
 *   Top of stairs 
1461
 */
1462
class SpiralStairway: Fixture
1463
    'perforated central black metal/stair/stairs/stairway/pole' 'stairs'
1464
    "The stairs are narrow triangles of perforated black metal arranged
1465
    in a helix around a central pole. "
1466
    isPlural = true
1467
;
1468
1469
topOfStairs: HouseRoom
1470
    roomName = 'Top of Stairs'
1471
    destName = 'the top of the stairs'
1472
    desc = "This is the top of a narrow spiral staircase that
1473
            leads down a dark shaft walled in rough brown bricks.
1474
            A passage leads south. "
1475
    south = stairPassage
1476
    down = spiralStairsTop
1477
;
1478
1479
+ Decoration 'dark rough brown shaft/brick/bricks' 'dark shaft'
1480
    "The shaft is just large enough for the staircase. "
1481
;
1482
1483
+ stairPassage: ThroughPassage ->bookcasePassage 'south passage' 'passage'
1484
;
1485
1486
+ spiralStairsTop: StairwayDown, SpiralStairway
1487
    moveActor(actor)
1488
    {
1489
        if (actor.isPlayerChar)
1490
            "{You/he} carefully descend{s} the steep, narrow stairs. ";
1491
        inherited(actor);
1492
    }
1493
1494
    travelBarrier = static [
1495
        new tvBarrier('The television is far too heavy to take down
1496
                      the stairs. '),
1497
        new tricycleBarrier('It would make a nice stunt, but it\'s far
1498
                            too dangerous to ride the tricycle down
1499
                            the stairs. ')
1500
    ]
1501
;
1502
1503
/* ------------------------------------------------------------------------ */
1504
/*
1505
 *   Bottom of stairs 
1506
 */
1507
bottomOfStairs: DarkHouseRoom
1508
    roomName = 'Bottom of Stairs'
1509
    destName = 'the bottom of the stairs'
1510
    desc = "This is the bottom of a narrow spiral staircase.
1511
            Apart from the stairs, the only exit is the door
1512
            to the south. "
1513
    up = spiralStairsBottom
1514
    south = stairDoor
1515
1516
    /* no illumination here */
1517
    brightness = 0
1518
1519
    /* 
1520
     *   even in the dark, keep the stairs in scope, because we're
1521
     *   standing on them 
1522
     */
1523
    getExtraScopeItems(actor)
1524
    {
1525
        return inherited(actor) + spiralStairsBottom;
1526
    }
1527
;
1528
1529
class IronDoor: LockableWithKey, Door
1530
    'iron door/rivet/rivets/plate/plates' 'iron door'
1531
    "It's a formidable-looking mass of iron plate and rivets. "
1532
1533
    keyList = [ironKey]
1534
;
1535
1536
+ stairDoor: IronDoor;
1537
1538
+ spiralStairsBottom: StairwayUp, SpiralStairway -> spiralStairsTop
1539
    travelBarrier: tricycleBarrier
1540
    {
1541
        msg_ = 'Riding the tricycle up the stairs is
1542
            simply out of the question. '
1543
    }
1544
;
1545
1546
/* 
1547
 *   make the washing machine a complex containe so that we can have
1548
 *   components as well as contents within 
1549
 */
1550
+ ComplexContainer 'washing machine/washer' 'washing machine'
1551
    "It's a beat-up old washing machine.  A big dial is the only
1552
    obvious control. "
1553
1554
    subContainer: ComplexComponent, OpenableContainer { }
1555
;
1556
1557
++ Component, LabeledDial
1558
    'big washer washing machine control dial' 'control dial'
1559
    "The dial has stops labeled Wash, Rinse, Spin, and Stop.  It's currently
1560
    set to <<curSetting>>. "
1561
1562
    curSetting = 'Wash'
1563
    validSettings = ['Wash', 'Rinse', 'Spin', 'Stop']
1564
;
1565
1566
/* ------------------------------------------------------------------------ */
1567
/*
1568
 *   Dining Room
1569
 */
1570
diningRoom: HouseRoom
1571
    roomName = 'Dining Room'
1572
    destName = 'the dining room'
1573
    desc = "This medium-sized room<<myNote.noteRef>> has a dining table
1574
            and a couple of chairs, one upholstered in dark fabric and
1575
            other in light fabric.
1576
            A door (<<diningDoor.openDesc>>) leads north. "
1577
    south = livingRoom
1578
    north = diningDoor
1579
    out asExit(north)
1580
    myNote: Footnote { "This is just a sample note for the dining room. " }
1581
;
1582
1583
+ alcove: OutOfReach, Fixture, Container 'arched small alcove' 'small alcove'
1584
    "It's a small, arched alcove, set high up on the wall, near the
1585
    ceiling. "
1586
1587
    useSpecialDesc = (contents.length() != 1 || contents[1] != trophy)
1588
    specialDesc = "A small alcove is set near the top of one wall,
1589
                   up near the ceiling. "
1590
1591
    canObjReachContents(obj)
1592
    {
1593
        /* if the actor is standing on a chair, allow it */
1594
        if (obj.posture == standing && obj.location == diningTable)
1595
            return true;
1596
1597
        /* inherit the default */
1598
        return inherited(obj);
1599
    }
1600
1601
    cannotReachFromOutsideMsg(obj)
1602
    {
1603
        return '{You/he} can\'t reach ' + obj.theNameObj
1604
            + ' from here; the alcove is too high up.  {You/he} might
1605
            be able to reach the alcove if {you/he} were standing
1606
            on something. ';
1607
    }
1608
;
1609
1610
++ trophy: Thing 'silver big trophy/award/cup' 'trophy'
1611
    "It's shaped like a big cup, and looks to be made of silver. "
1612
1613
    useInitSpecialDesc = (location == alcove)
1614
    initSpecialDesc = "A trophy is visible in a small alcove set
1615
        into the top of one wall. "
1616
;
1617
1618
+ diningDoor: Door 'door' 'door'
1619
    "It leads north. "
1620
    travelBarrier = [tricycleBarrier, tvBarrier]
1621
;
1622
1623
class DiningChair: Chair, Immovable 'chair*chairs' 'chair'
1624
    "Its style matches that of the table. "
1625
;
1626
1627
+ DiningChair 'light lighter fabric' 'lighter chair';
1628
+ DiningChair 'dark darker fabric' 'darker chair';
1629
1630
+ diningTable: Heavy, Platform 'dining large oval table' 'table'
1631
    "It's a large<<myNote.noteRef>> oval table. "
1632
    myNote: Footnote { "Okay, it's not all that large.  Medium-sized
1633
                        sounds so weak, though. " }
1634
;
1635
1636
class MyCandle: FireSource, Candle
1637
    fuelLevel = 32
1638
    desc()
1639
    {
1640
        /* show a description based on how far we've burned down */
1641
        if (fuelLevel > 30)
1642
            "The candle has barely been used. ";
1643
        else if (fuelLevel > 24)
1644
            "The candle looks only slightly burned down. ";
1645
        else if (fuelLevel > 16)
1646
            "The candle looks about half burned down. ";
1647
        else if (fuelLevel > 8)
1648
            "The candle is considerably burned down. ";
1649
        else if (fuelLevel > 0)
1650
            "The candle is mostly burned down. ";
1651
        else
1652
            "The candle is completely burned down. ";
1653
1654
        /* if we're burning, mention it */
1655
        if (isLit)
1656
            "It's lit. ";
1657
    }
1658
;
1659
1660
++ MyCandle 'red candle' 'red candle';
1661
++ MyCandle 'white candle' 'white candle';
1662
1663
++ vase: Container 'vase' 'vase'
1664
    "It's a simple glass cylinder, open at the top. "
1665
    bulkCapacity = 1
1666
    initSpecialDesc = "A bouquet of flowers is arranged in a vase
1667
        in the center of the table. "
1668
    useInitSpecialDesc()
1669
    {
1670
        /* don't use the initial description if the flowers have been moved */
1671
        return flowers.isIn(self) ? inherited() : nil;
1672
    }
1673
    verifyInsert(obj, newCont)
1674
    {
1675
        if (obj != flowers)
1676
            illogical('The vase is only designed to hold flowers. ');
1677
    }
1678
1679
    /* 
1680
     *   in room and inventory lists, show the vase with its flowers, if
1681
     *   it has the flowers 
1682
     */
1683
    listName = (flowers.isIn(self)
1684
                ? 'a bouquet of flowers in a vase'
1685
                : inherited())
1686
;
1687
1688
+++ flowers: Thing 'flower/flowers/arrangement/bouquet' 'bouquet of flowers'
1689
    "It's a nice arrangement of flowers. "
1690
1691
    /* 
1692
     *   don't separately list the flowers if they're in the vase, because
1693
     *   the vase shows the flowers as part of its list name; but don't
1694
     *   hide the flowers from an explicit "look in vase" 
1695
     */
1696
    isListed = (!isIn(vase))
1697
    isListedIn = true
1698
;
1699
1700
++++ Odor 'floral flowery pleasant fragrance/odor/smell' 'floral fragrance'
1701
    /* the description when they smell the flowers directly */
1702
    sourceDesc = "The flowers have a strong, well, flowery fragrance. "
1703
1704
    /* the description when they smell us and the flowers are visible */
1705
    descWithSource = "The pleasant fragrance is coming from the flowers. "
1706
1707
    /* the description when they smell us and the flowers aren't seen */
1708
    descWithoutSource = "It's a pleasant floral fragrance. "
1709
1710
    /* room description with and without being able to see the flowers */
1711
    hereWithSource = "The flowers have a pleasant fragrance. "
1712
    hereWithoutSource = "A flowery fragrance is in the air. "
1713
;
1714
1715
++ StretchyContainer 'stretchy bag' 'stretchy bag'
1716
    "It's made of some very elastic material.  It seems
1717
    to take on the size and shape of whatever's inside. "
1718
1719
   /* 
1720
    *   we have no bulk contribution of our own when we have any contents,
1721
    *   but we do have a minimum empty bulk of 1 
1722
    */
1723
   bulk = 0
1724
   minBulk = 1
1725
;
1726
1727
++ Container 'cookie tin' 'cookie tin'
1728
    "It's a round metal cylinder about three inches tall, with
1729
    no lid, decorated with pictures of cookies. "
1730
1731
    bulkCapacity = 3
1732
;
1733
1734
++ Thing 'yellow pile inflatable rubber raft' 'inflatable rubber raft'
1735
    desc
1736
    {
1737
        if (inflated)
1738
            "It's fully inflated. ";
1739
        else
1740
            "In its current deflated state, it's just a pile of yellow
1741
            rubber, bunched up messily.  It's large, but you could
1742
            probably inflate it if you had to. ";
1743
    }
1744
1745
    inflated = nil
1746
    bulk { return inflated ? 10 : 2; }
1747
1748
    makeInflated(flag)
1749
    {
1750
        /* check the effect of the inflation on my bulk */
1751
        whatIf({: checkBulkChange()}, &inflated, flag);
1752
1753
        /* set the new status */
1754
        inflated = flag;
1755
    }
1756
1757
    dobjFor(Inflate)
1758
    {
1759
        verify()
1760
        {
1761
            if (inflated)
1762
                illogicalAlready('It\'s already fully inflated. ');
1763
        }
1764
        action()
1765
        {
1766
            makeInflated(true);
1767
            mainReport('It takes all your strength, but you manage to
1768
                blow up the raft.  You might feel light-headed for
1769
                a bit. ');
1770
        }
1771
    }
1772
1773
    dobjFor(Deflate)
1774
    {
1775
        verify()
1776
        {
1777
            if (!inflated)
1778
                illogicalAlready('It\'s already fully deflated. ');
1779
        }
1780
        action()
1781
        {
1782
            makeInflated(nil);
1783
            mainReport('You let the air out of the raft, which flattens
1784
                into a bunched-up pile of yellow rubber. ');
1785
        }
1786
    }
1787
;
1788
1789
/* ------------------------------------------------------------------------ */
1790
/* 
1791
 *   Back Yard 
1792
 */
1793
backYard: OutdoorRoom
1794
    roomName = 'Back Yard'
1795
    destName = 'the back yard'
1796
    vocabWords = 'back yard'
1797
    desc = "This small yard has a well-kept lawn.  The edges of the yard
1798
            are defined by thick, tall hedges.  An old garden shed occupies
1799
            the northwest corner of the yard.  A door into the house
1800
            is to the south. "
1801
    south = yardDoor
1802
    in asExit(south)
1803
    northwest = shedOuterDoor
1804
;
1805
+ yardDoor: Door ->diningDoor 'house back door' 'back door of the house'
1806
    "It leads south, into the house.  "
1807
;
1808
1809
+ Decoration 'shrub/shrubs/hedge/hedges/plant/plants' 'hedges'
1810
    "The hedges are tall and thick, and completely close in the
1811
    boundaries of the yard. "
1812
    isPlural = true
1813
    dobjFor(LookOver)
1814
    {
1815
        verify() { }
1816
        action() { "The hedges are too tall. "; }
1817
    }
1818
;
1819
1820
/*
1821
 *   This object tests and demonstrates using '*' and matchName to parse
1822
 *   non-dictionary words as object names.
1823
 *   
1824
 *   This isn't the kind of place you'd really use '*' in practice; for
1825
 *   this case, you'd be much better off defining all of the possible
1826
 *   color names as adjectives for the object and then using matchName()
1827
 *   to ignore matches to anything but the current one.  This works better
1828
 *   because the color names would then exist as dictionary words, and
1829
 *   hence the parser would not complain about them being unknown when
1830
 *   this object is not in scope.
1831
 */
1832
+ Sphere: Immovable
1833
    noun = '*' 'sphere'
1834
    name = (colors[curColor] + ' sphere')
1835
    desc = "The <<colors[curColor]>> sphere floats unnervingly in mid-air,
1836
            with no apparent mechanical means of suspension.  It does not
1837
            hover like a balloon, but seems absolutely still, as though
1838
            firmly attached to an invisible pole. "
1839
    cannotTakeMsg = 'The sphere is strangely fixed in place; it resists
1840
                     your attempts to move it.  It does seem to be able to
1841
                     rotate freely on its vertical axis, though, so you
1842
                     could probably turn it if you wanted to. '
1843
    cannotMoveMsg = (cannotTakeMsg)
1844
    cannotPutInMsg = (cannotTakeMsg)
1845
1846
    initSpecialDesc = "In the center of the yard, floating in
1847
        mid-air, is a <<colors[curColor]>> sphere. "
1848
1849
    matchNameCommon(origTokens, adjustedTokens)
1850
    {
1851
        /* check each token */
1852
        foreach (local cur in origTokens)
1853
        {
1854
            /* 
1855
             *   if it's 'sphere' or our current color name, match it;
1856
             *   otherwise, we don't have a match 
1857
             */
1858
            if (getTokVal(cur) not in ('sphere', colors[curColor]))
1859
                return nil;
1860
        }
1861
1862
        /* all of our tokens match */
1863
        return self;
1864
    }
1865
1866
    dobjFor(Turn)
1867
    {
1868
        verify() { }
1869
        action()
1870
        {
1871
            ++curColor;
1872
            if (curColor > colors.length())
1873
                curColor = 1;
1874
1875
            "Although the sphere is immovably fixed in space, it rotates
1876
            so freely that it feels weightless.  You give it the lightest
1877
            touch, and it starts spinning wildly, so fast that it becomes
1878
            a featureless, glowing blur.  After a few moments, it abruptly
1879
            comes to a stop, and you see that its color has changed
1880
            to <<colors[curColor]>>. ";
1881
        }
1882
    }
1883
1884
    /* current index in color list */
1885
    curColor = 1
1886
1887
    /* list of possible colors */
1888
    colors = ['red', 'blue', 'green', 'orange', 'yellow', 'white']
1889
;
1890
1891
+ Enterable, Decoration ->yardDoor 'house' 'house'
1892
    "It's a modest one-story house.  A door leads in to the south. "
1893
;
1894
1895
+ Enterable ->shedOuterDoor 'old garden shed' 'shed'
1896
    "The shed's wood siding looks well weathered; only the thinnest
1897
    layer of white paint remains.  Its flimsy-looking door
1898
    is <<shedOuterDoor.openDesc>>. "
1899
;
1900
1901
+ Decoration 'shed\'s wood siding' 'wood siding'
1902
    "It looks weathered. "
1903
;
1904
1905
+ shedOuterDoor: Door 'shed door' 'door of the shed'
1906
    "It leads into the shed.  "
1907
;
1908
1909
/* ------------------------------------------------------------------------ */
1910
/*
1911
 *   Shed 
1912
 */
1913
shed: Room 'Shed' 'the shed'
1914
    "There's barely room to stand up or turn around in this tiny
1915
    storage space.  The north wall has a few deep shelves, but
1916
    otherwise the structure is featureless.  A door leads out
1917
    to the southeast. "
1918
    southeast = shedInnerDoor
1919
    out asExit(southeast)
1920
;
1921
1922
+ shedInnerDoor: Door ->shedOuterDoor 'shed door' 'door'
1923
    "It leads out of the shed. "
1924
;
1925
1926
+ shedShelves: Fixture, Surface 'deep shelf/shelves' 'shelf'
1927
    "The shelves are about a yard deep and look sturdy. "
1928
;
1929
1930
++ Flashlight 'flash light/flashlight' 'flashlight'
1931
    "It's a big lantern-type flashlight.  It's currently <<onDesc>>. "
1932
;
1933
1934
++ Thing 'rat poison/box' 'box of rat poison'
1935
    "It's a large box, missing its top, full of white powder. The
1936
    box is labeled RAT POISON and is marked with a prominent
1937
    skull-and-crossbones symbol, along with a legend in tiny
1938
    type reading <q>Use of this product in a manner inconsistent
1939
    with its labeling is a violation of Adventure Game Law.</q> "
1940
1941
    dobjFor(Eat) remapTo(Eat, ratPoison)
1942
    lookInDesc = "It's full of white powder. "
1943
;
1944
1945
+++ ratPoison: Component, Food 'white powder' 'white powder'
1946
    "The box is full of white powder. "
1947
    cannotTakeMsg = 'You\'d best leave the poison in the box. '
1948
    cannotMoveMsg = 'You\'d best leave the poison in the box. '
1949
    cannotPutMsg = 'Best to leave the poison in the box. '
1950
1951
    dobjFor(Pour)
1952
    {
1953
        verify() { }
1954
        action() { "You'd best leave the poison in its box. "; }
1955
    }
1956
    dobjFor(PourInto) asDobjFor(Pour)
1957
    dobjFor(PourOnto) asDobjFor(Pour)
1958
1959
    dobjFor(Eat)
1960
    {
1961
        preCond = [touchObj]
1962
        verify() { dangerous; }
1963
        action()
1964
        {
1965
            "It's not very tasty, but it sure is deadly. ";
1966
1967
            /* terminate the game */
1968
            finishGameMsg(ftDeath, [finishOptionUndo, finishOptionCredits]);
1969
        }
1970
    }
1971
;
1972
1973
class MyMatch: Matchstick 'match*matches' 'match'
1974
;
1975
1976
++ Matchbook 'match book/matches/matchbook*matches' 'book of matches'
1977
    matchName(origTokens, adjustedTokens)
1978
    {
1979
        /* 
1980
         *   if the name is just 'match', ignore it - we want to allow
1981
         *   'match' as an adjective because we want to match 'match
1982
         *   book', but just plain 'match' is very unlikely to mean us 
1983
         */
1984
        if (origTokens.length() == 1
1985
            && getTokVal(origTokens[1]) == 'match')
1986
            return nil;
1987
1988
        /* inherit default handling */
1989
        return inherited(origTokens, adjustedTokens);
1990
    }
1991
1992
    /* put this after the individual matches when we match a plural */
1993
    pluralOrder = 110
1994
;
1995
1996
+++ MyMatch;
1997
+++ MyMatch;
1998
+++ MyMatch;
1999
+++ MyMatch;
2000
+++ MyMatch;
2001
2002
/* ------------------------------------------------------------------------ */
2003
/*
2004
 *   Additional verbs
2005
 */
2006
2007
DefineTAction(Inflate);
2008
VerbRule(Inflate)
2009
    ('inflate' | 'blow' 'up') dobjList
2010
    | 'blow' dobjList 'up'
2011
    : InflateAction
2012
    verbPhrase = 'inflate/inflating (what)'
2013
;
2014
2015
DefineTAction(Deflate);
2016
VerbRule(Deflate)
2017
    'deflate' dobjList
2018
    : DeflateAction
2019
    verbPhrase = 'deflate/deflating (what)'
2020
;
2021
2022
DefineTAction(LookOver);
2023
VerbRule(LookOver)
2024
    'look' 'over' dobjList
2025
    : LookOverAction
2026
    verbPhrase = 'look/looking (over what)'
2027
;
2028
2029
DefineTAction(Ride);
2030
VerbRule(Ride)
2031
    'ride' singleDobj : RideAction
2032
    verbPhrase = 'ride/riding (what)'
2033
;
2034
2035
DefineTAction(Answer);
2036
VerbRule(Answer)
2037
    'answer' dobjList
2038
    : AnswerAction
2039
    verbPhrase = 'answer/answering (what)'
2040
;
2041
2042
modify Thing
2043
    dobjFor(Inflate)
2044
    {
2045
        preCond = [touchObj]
2046
        verify()
2047
        {
2048
            illogical('{That dobj/he} {is}n\'t something {you/he} can
2049
                inflate. ');
2050
        }
2051
    }
2052
    dobjFor(Deflate)
2053
    {
2054
        preCond = [touchObj]
2055
        verify()
2056
        {
2057
            illogical('{That dobj/he} {is}n\'t something {you/he} can
2058
                deflate. ');
2059
        }
2060
    }
2061
    dobjFor(LookOver)
2062
    {
2063
        preCond = [objVisible]
2064
        verify() { illogical('{You/he} see{s} nothing unusual. '); }
2065
    }
2066
    dobjFor(Ride)
2067
    {
2068
        verify()
2069
        {
2070
            illogical('{That dobj/he} {is}n\'t something {you/he}
2071
                can ride. ');
2072
        }
2073
    }
2074
    dobjFor(Answer)
2075
    {
2076
        preCond = [objVisible]
2077
        verify()
2078
        {
2079
            illogical('{The dobj/he} didn\'t ask {you/him} anything. ');
2080
        }
2081
    }
2082
;
2083
2084
DefineTopicAction(ThinkAbout)
2085
    execAction()
2086
    {
2087
        "You think about <<gTopic.getTopicText()>>. ";
2088
    }
2089
;
2090
VerbRule(ThinkAbout) 'think' 'about' singleTopic : ThinkAboutAction
2091
    verbPhrase = 'think/thinking (about what)'
2092
;
2093
2094
/* ------------------------------------------------------------------------ */
2095
/*
2096
 *   Basement tunnel 
2097
 */
2098
tunnel: DarkRoom 'Tunnel' 'the tunnel'
2099
    "This low, narrow tunnel is walled in old, dark gray concrete
2100
    or something similar.  The walls curve inward at shoulder level to
2101
    form an arched ceiling just barely high enough at the center
2102
    to allow walking without bending your neck.  The passage ends
2103
    to the north at an iron door, and ends at the south in an
2104
    earthen wall. A narrow side passage leads west. "
2105
2106
    north = tunnelDoor
2107
    west = sideTunnel
2108
2109
    /* 
2110
     *   since we have special walls and ceiling that are specifically
2111
     *   included as normal decoration objects in this location, keep only
2112
     *   the default floor among our room parts 
2113
     */
2114
    roomParts = [defaultFloor]
2115
;
2116
2117
+ tunnelCeiling: Decoration
2118
    'arched concrete cement ceiling roof' 'ceiling'
2119
    "It's very low, and arches in from the walls. "
2120
;
2121
2122
+ tunnelWalls: Decoration
2123
    'east west tunnel concrete cement wall/walls' 'tunnel wall'
2124
    "The walls are made of some kind of dark gray concrete. "
2125
;
2126
2127
+ earthenWall: Decoration
2128
    'earthen dirt south wall' 'earthen wall'
2129
    "It's just a wall of dirt, as though construction on the
2130
    tunnel had been abruptly halted here. "
2131
;
2132
2133
+ tunnelDoor: IronDoor ->stairDoor;
2134
2135
/* ------------------------------------------------------------------------ */
2136
/*
2137
 *   Side tunnel 
2138
 */
2139
sideTunnel: DarkRoom 'Jagged Tunnel' 'the jagged tunnel'
2140
    "This is an extremely narrow tunnel; it looks like construction
2141
    was abandoned before it was completed. The passage is jagged,
2142
    but continues a little way to the east.  A rough ladder has
2143
    been improvised from wood scraps and fastened to the wall,
2144
    leading up to a small trap door above. "
2145
2146
    up = tunnelTrapDoor
2147
    east = tunnel
2148
2149
    roomParts = [defaultFloor]
2150
2151
    getExtraScopeItems(actor)
2152
    {
2153
        /* 
2154
         *   if they arrived from above, make sure the trap door and
2155
         *   ladder are in scope 
2156
         */
2157
        if (lastArrivedVia[actor] == tunnelTrapDoor)
2158
            return inherited(actor) + tunnelTrapDoor + tunnelLadder;
2159
        else
2160
            return inherited(actor);
2161
    }
2162
2163
    /* table giving last arrival connector for each actor present */
2164
    lastArrivedVia = static new LookupTable(4, 4)
2165
2166
    travelerArriving(traveler, origin, connector, backConnector)
2167
    {
2168
        /* note the connector by which they're arriving */
2169
        foreach (local actor in traveler.getTravelerActors())
2170
            lastArrivedVia[actor] = backConnector;
2171
2172
        /* inherit default handling */
2173
        inherited(traveler, origin, connector, backConnector);
2174
    }
2175
;
2176
2177
+ tunnelTrapDoor: Fixture, Door
2178
    'small trap door' 'trap door'
2179
;
2180
2181
+ tunnelLadder: StairwayUp 'rough improvised wood ladder/scraps' 'ladder'
2182
    dobjFor(TravelVia) remapTo(TravelVia, tunnelTrapDoor)
2183
;
2184
2185
+ Decoration
2186
    'west north south tunnel dirt jagged wall/walls' 'tunnel wall'
2187
    "The walls are just bare dirt. "
2188
;
2189
2190
/* ------------------------------------------------------------------------ */
2191
/*
2192
 *   A generic USE verb of two objects.  We'll remap this to a more
2193
 *   specific verb when possible, based on the direct object, using the
2194
 *   direct object's remapDobjUse() method.  
2195
 */
2196
DefineTIAction(UseWith)
2197
    /* we want to remap on the direct object, so we must resolve it first */
2198
    resolveFirst = DirectObject
2199
;
2200
VerbRule(UseWith) 'use' singleDobj ('on' | 'with') singleIobj: UseWithAction
2201
    verbPhrase = 'use/using (what) (on what)'
2202
;
2203
VerbRule(UseWithWhat) 'use' singleDobj: UseWithAction
2204
    verbPhrase = 'use/using (what) (on what)'
2205
    construct()
2206
    {
2207
        /* set up the empty indirect object phrase */
2208
        iobjMatch = new EmptyNounPhraseProd();
2209
        iobjMatch.responseProd = withSingleNoun;
2210
    }
2211
;
2212
2213
modify Thing
2214
    dobjFor(UseWith) { verify() { } }
2215
    iobjFor(UseWith) { verify() { illogical('Please be more specific. '); }}
2216
;
2217
2218
/*
2219
 *   USE <key> WITH <obj> maps to LOCK/UNLOCK <obj> WITH <key> 
2220
 */
2221
modify Key
2222
    dobjFor(UseWith)
2223
    {
2224
        verify() { verifyIobjLockWith(); }
2225
        preCond() { return preCondIobjLockWith(); }
2226
        remap()
2227
        {
2228
            /* 
2229
             *   If we have a single tentative indirect object, check to
2230
             *   see if it's locked or unlocked, and use the appropriate
2231
             *   verb (LOCK or UNLOCK) to reverse its state.  If we can't
2232
             *   yet identify the indirect object, guess that it's UNLOCK.
2233
             */
2234
            if (gTentativeIobj.length() == 1)
2235
            {
2236
                /* 
2237
                 *   If the object is locked, unlock it; otherwise lock
2238
                 *   it.  Note that we must reverse the dobj/iobj roles in
2239
                 *   the remapped verb, because the key is the direct
2240
                 *   object of USE but the indirect object of LOCK/UNLOCK. 
2241
                 */
2242
                if (gTentativeIobj[1].obj_.isLocked)
2243
                    return [UnlockWithAction, OtherObject, self];
2244
                else
2245
                    return [LockWithAction, OtherObject, self];
2246
            }
2247
            else
2248
            {
2249
                /* guess that it's UNLOCK */
2250
                return [UnlockWithAction, OtherObject, self];
2251
            }
2252
        }
2253
    }
2254
;
2255
2256
/* ------------------------------------------------------------------------ */
2257
/*
2258
 *   "fill x with y" - treat this as "put y in x".  This verb isn't really
2259
 *   useful in this sample game; it's here only as a demonstration of
2260
 *   using the TIAction remapping macros.  
2261
 */
2262
DefineTIAction(FillWith)
2263
    resolveFirst = DirectObject
2264
;
2265
2266
VerbRule(FillWith)
2267
    'fill' singleDobj 'with' iobjList
2268
    : FillWithAction
2269
    verbPhrase = 'fill/filling (what) (with what)'
2270
;
2271
2272
modify Thing
2273
    dobjFor(FillWith) remapTo(PutIn, IndirectObject, self);
2274
;
2275
2276
/* ------------------------------------------------------------------------ */
2277
/*
2278
 *   Main startup object.  This lets us customize the introductory text,
2279
 *   set the player character object, and control the game's startup
2280
 *   procedure.  
2281
 */
2282
gameMain: GameMainDef
2283
    /* the initial (and only) player character is 'me' */
2284
    initialPlayerChar = me
2285
2286
    /* show our introduction message */
2287
    showIntro()
2288
    {
2289
        /* show our simple introduction message */
2290
        "Welcome to the TADS 3 Library Sample Game!\b";
2291
    }
2292
2293
    /* 
2294
     *   Create an "about box".
2295
     *   
2296
     *   The <ABOUTBOX> tag has to be re-displayed each time we clear the
2297
     *   screen, since the tag is part of the main text window's output
2298
     *   stream and thus only lasts as long as the rest of the text on the
2299
     *   main game screen.  Fortunately, the library will take care of this
2300
     *   for us automatically, as long as we do two things.  First, we have
2301
     *   to put our <ABOUTBOX> tag here, in this method, so that the
2302
     *   library knows how to re-display the tag when necessary.  Second,
2303
     *   we must always use the cls() function (NOT the low-level
2304
     *   clearScreen() function) whenever we want to clear the game window.
2305
     */
2306
    setAboutBox()
2307
    {
2308
        "<aboutbox><body bgcolor=black text=white>
2309
        <table height='100%' width='100%' border=0>
2310
        <tr valign=middle align=center><td>
2311
        <font size=5 face=tads-sans>T3 Library Sampler</font><br><br>
2312
        <font face=tads-sans><b>This is the T3 Library Sample Game.  It's
2313
        just an example of how to create a game using the T3 standard
2314
        library (also known as <q>adv3</q>).</b></font>
2315
        </table></aboutbox>";
2316
    }
2317
2318
    /* show our end-of-game message */
2319
    showGoodbye()
2320
    {
2321
        "<.p>Thanks for playing the Library Sample Game!\b";
2322
    }
2323
;
2324
2325
/* ------------------------------------------------------------------------ */
2326
/*
2327
 *   random daemon/fuse tests
2328
 */
2329
2330
VerbRule(rtfuse) 'test-rtfuse' : IAction
2331
    execAction()
2332
    {
2333
        "Starting a real-time fuse for 10 seconds... ";
2334
        new RealTimeFuse(testFuse, &execEvent, 10000);
2335
    }
2336
;
2337
2338
VerbRule(rtdaemon) 'test-rtdaemon' : IAction
2339
    execAction()
2340
    {
2341
        "Starting a real-time daemon for 10 second... ";
2342
        new RealTimeDaemon(testDaemon, &execEvent, 10000);
2343
    }
2344
;
2345
2346
VerbRule(moreprompt) 'moreprompt' : IAction
2347
    execAction()
2348
    {
2349
        "Pausing for a MORE prompt, leaving the real-time clock running.\n";
2350
        inputManager.pauseForMore(nil);
2351
    }
2352
;
2353
2354
VerbRule(morefreeze) 'morefreeze' : IAction
2355
    execAction()
2356
    {
2357
        "Pausing for a MORE prompt, freezing the real-time clock.\n";
2358
        inputManager.pauseForMore(true);
2359
    }
2360
;
2361
2362
VerbRule(fuse) 'test-fuse' : IAction
2363
    execAction()
2364
    {
2365
        "Starting a fuse for three turns... ";
2366
        new Fuse(testFuse, &execEvent, 3);
2367
    }
2368
;
2369
2370
testFuse: object
2371
    execEvent()
2372
    {
2373
        "<.commandsep>This is the test fuse!";
2374
    }
2375
;
2376
2377
VerbRule(daemon) 'test-daemon': IAction
2378
    execAction()
2379
    {
2380
        "Starting daemon that will run three times, every other turn... ";
2381
        new Daemon(testDaemon, &execEvent, 2);
2382
        testDaemon.counter = 0;
2383
    }
2384
;
2385
2386
VerbRule(getkey) 'getkey': IAction
2387
    execAction()
2388
    {
2389
        local key;
2390
2391
        /* get a key */
2392
        key = inputManager.getKey(true, {: "<.p>Enter a key:\ " });
2393
2394
        /* show the key */
2395
        "\nThanks!  You typed '<<key>>'!\n";
2396
2397
        nestedActorAction(bob, Take, bigRedBall);
2398
    }
2399
;
2400
2401
VerbRule(showlinks) 'showlinks': IAction
2402
    execAction()
2403
    {
2404
        "Here are some links:
2405
        <ul>
2406
        <li><font color=green>color <a href='outside'>outside</a>
2407
            the link</font>
2408
        <li><font color=purple>color <a href='inside'><font color=red>
2409
            inside</font></a> the link</font>
2410
        <li><font color=navy>a mix of <a href='mix'>outside
2411
            <font color=#ff8000>and inside</font> the link</a> for
2412
            extra completeness</font>
2413
        </ul> ";
2414
    }
2415
;
2416
2417
testDaemon: object
2418
    execEvent()
2419
    {
2420
        "\bThis is the test daemon! ";
2421
2422
#if 0
2423
        "<.p>*** You have died ***\n";
2424
        finishGame([finishOptionUndo, finishOptionCredits,
2425
                    finishOptionFullScore]);
2426
#endif
2427
2428
        /* if I've run three times now, cancel myself */
2429
        if (++counter == 3)
2430
            eventManager.removeCurrentEvent();
2431
    }
2432
    counter = 0
2433
;
2434
2435
VerbRule(nbspTest) 'nbsptest' : IAction
2436
    execAction()
2437
    {
2438
        "(This test is designed for 80-column displays.)
2439
        \b
2440
This is a long line intended to wrap after a bit.  The trick is that
2441
here&nbsp;to&nbsp;here (i.e., the parts between the first and second 'here')
2442
should be non-breaking: 'here&nbsp;to&nbsp;here' should all be on one line,
2443
even though there are spaces between the words.  Anyway, so much for
2444
the test. ";
2445
    }
2446
;
2447
2448
VerbRule(wrapTest) 'wraptest' : IAction
2449
    execAction()
2450
    {
2451
        "(This test is designed for 80-column displays.)
2452
        \b
2453
This is a long line intended to wrap after.\u2002First, let's test soft
2454
hy&shy;phen&shy;ation.
2455
Okay, we should have hyphenated somewhere in there.
2456
\b
2457
Next, let us make sure that regular hyphen wrapping still works.  What we need
2458
is for this line to wrap <b>after</b> 'need'.\n
2459
Next, let us make sure that regular hyphen wrapping still works.  What we
2460
need&nbsp;-- and this is key&nbsp;-- is to make sure this line wraps
2461
<b>before</b> 'need'.\n
2462
Next, let us make sure that regular hyphens still work.  This time use
2463
hard-hyphen-wrapping, where we break at a hyphen in the middle of a word.
2464
\b
2465
Now we can test some new stuff.  First, test the explicit break flag:
2466
We\u200bCan\u200bBreak\u200bAt\u200bAny\u200bOf\u200bThese";
2467
"\u200bInter\u200bCaps\u200bCharacters\u200bAnd\u200bWe\u200bCan\u200bGo";
2468
"\u200bOn\u200bLike\u200bThis\u200bFor\u200bQuite\u200bSome\u200bTime";
2469
"\u200bEven\u200bTo\u200bThe\u200bExtent\u200bOf\u200bWrapping\u200bAnother";
2470
"\u200bLine\u200bOf\u200bText!
2471
\b
2472
Next, let's test that explicit non-break points are working.  We'll want
2473
to\ufeff \ufeffbreak just before the previous word 'break', but we have a
2474
non-breaking zero-width space after the 'to' and another before the 'break',
2475
which should prevent breaking there.\n
2476
Likewise, we'll want to break this line at a hyphen, coming up right
2477
here-\ufeffabsolutely.  But we have a non-break right after the hyphen,
2478
which should tell us not to break there.\n
2479
For comparison, this line we do want to break at the hyphen, which is
2480
here-absolutely once again.  This time we should break after the hyphen.
2481
\b
2482
The next test is for East Asian language wrapping.<wrap char>WeAreNowIn";
2483
"CharacterWrapMode,WhichMeansThatWeCanWrapBetweenAnyTwoCharactersExcept";
2484
"WhereOtherwiseNotedWithAnExplicitNonBreakingIndicator\ufeff.<wrap word>This
2485
is back in English-style text, which means we should wrap on word
2486
boundaries.<wrap char>AndNowWe'reBackInCharacterWrappingMode.TheTrickIsThat";
2487
"WeWantToCheckTransitionsBetweenTheTwoModes.<wrap word>Such as here, where
2488
we are back in word mode.<wrap char>OrHereWithCharInTheMiddleOfALine.";
2489
"<wrap word>We're back in word-wrap mode here.\u2002The remaining test is
2490
to put word-wrap mode in the middle of a line of<wrap char>CharacterWrapped";
2491
"Text,SuchAsThis<wrap word>(here is some word-wrapped text)<wrap char>AndNow";
2492
"WeAreBackToCharacterWrappedTextToTheEndOfTheLine.ATestWeHaveYetToTryWith";
2493
"CharWrappedTextIs\ufeff.\ufeff.\ufeff.ExplicitNonBreakingIndicators.
2494
<wrap word>(That ellipsis should be all together, not broken across lines,
2495
and should furthermore stick to the letter just before it, so the ellipsis
2496
doesn't start a new line.)\n
2497
\b
2498
This is just a test of <u>underlining some typographical spaces</u>:
2499
let's try <u>an en space:\u2002an em space:\u2003a thin space:\u2009a
2500
punctuation space:\u2008a figure space:\u2007a hair space:\u200a...and
2501
that</u> should do it.\n
2502
<font color=black bgcolor=yellow>
2503
And now let's try something similar:\u2002
2504
ending the line with an en plus an em:\u2002\u2003This is to demonstrate
2505
that we trim trailing whitespace, even if the whitespace consists of
2506
typographical spaces.</font>\n
2507
<font color=black bgcolor=aqua>
2508
Another similar test:\u2002This time, some non-breaking
2509
spaces:\uFEFF\u2002\uFEFF\u2003\uFEFF\n
2510
That ended with an en plus em again, but these were quoted with FEFF's.
2511
</font>\n
2512
\b
2513
<p><table border width=50% align=center>
2514
<tr><td><wrap char>ThisIsSomeTextInATableInCharacterWrapMode.TheIdeaIsTo";
2515
"SeeHowTablesDealWithThisSortOfThing.TablesUseTheBasicWrappingMechanism";
2516
"LikeEverythingElse,ButHaveSomeSpecialCodeToMeasureTheSizeOfTheTextThat";
2517
"WillGoInThem.<wrap word>
2518
<td>This is a second column that uses character-based wrapping instead
2519
of word-based wrapping.  The two columns should more or less balance
2520
out, although the word-based column will want a bigger share because
2521
of its larger minimum width.
2522
</table>\n";
2523
    }
2524
;
2525
2526
2527
VerbRule(logAbout) 'logabout' : IAction
2528
    execAction()
2529
    {
2530
        "Logging the ABOUT command to About.txt...\n";
2531
        LogConsole.captureToFile('About.txt',
2532
                                 getLocalCharSet(CharsetDisplay), 80,
2533
                                 {: AboutAction.execSystemAction() });
2534
    }
2535
;
2536
2537
/* ------------------------------------------------------------------------ */
2538
/*
2539
 *   A rather contrived preparser.
2540
 *   
2541
 *   This preparser allows the player to set "aliases" using syntax like
2542
 *   this:
2543
 *   
2544
 *   alias $xxx yyy
2545
 *   
2546
 *   where xxx is the name of an alias variable, and yyy is the
2547
 *   replacement text for the variable.  Once an alias has been created,
2548
 *   it can be used in any command simply by naming it with the '$ prefix.
2549
 */
2550
StringPreParser
2551
    /* pre-compile our search patterns */
2552
    patDefAlias = static new RexPattern(
2553
        '<space>*alias<space>+<dollar>(<alphanum>+)<space>+(.+)')
2554
    patUseAlias = static new RexPattern(
2555
        '(<^dollar>*)<dollar>(<alphanum>+)(.*)')
2556
2557
    doParsing(str, which)
2558
    {
2559
        local ret;
2560
        local aliasName;
2561
        local aliasText;
2562
        
2563
        /* check for the alias definition syntax */
2564
        if (rexMatch(patDefAlias, str) != nil)
2565
        {
2566
            /* extract the alias name and the replacement text */
2567
            aliasName = rexGroup(1)[3];
2568
            aliasText = rexGroup(2)[3];
2569
2570
            /* add the alias to our table */
2571
            aliasTable[aliasName] = aliasText;
2572
2573
            /* mention that the alias has been defined */
2574
            "Okay, $<<aliasName>> has been defined. ";
2575
2576
            /* this command has been fully handled */
2577
            return nil;
2578
        }
2579
2580
        /* we're not defining an alias, so look for aliases to replace */
2581
        ret = nil;
2582
        for (;;)
2583
        {
2584
            /* look for a '$' in the balance of the input string */
2585
            if (rexMatch(patUseAlias, str) != nil)
2586
            {
2587
                local prv;
2588
                local nxt;
2589
                
2590
                /* 
2591
                 *   extract the text before the alias, the name of the
2592
                 *   alias, and the rest of the text after the alias 
2593
                 */
2594
                prv = rexGroup(1)[3];
2595
                aliasName = rexGroup(2)[3];
2596
                nxt = rexGroup(3)[3];
2597
2598
                /* add the previous text to the output string so far */
2599
                if (ret != nil)
2600
                    ret += prv;
2601
                else
2602
                    ret = prv;
2603
2604
                /* 
2605
                 *   translate the alias; if it has no translation, then
2606
                 *   use the original text of the alias, including the
2607
                 *   '$', unchanged 
2608
                 */
2609
                aliasText = aliasTable[aliasName];
2610
                if (aliasText == nil)
2611
                    aliasText = '$' + aliasName;
2612
2613
                /* add the translated alias to the output string so far */
2614
                ret += aliasText;
2615
2616
                /* continue parsing the balance of the input string */
2617
                str = nxt;
2618
            }
2619
            else
2620
            {
2621
                /* 
2622
                 *   We have no more aliases in the string - the balance
2623
                 *   of the string is simply to be returned unchanged.  If
2624
                 *   we've already built some output text, add the balance
2625
                 *   of the input to the output so far; otherwise, the
2626
                 *   balance of the input is the entirety of the output.  
2627
                 */
2628
                if (ret != nil)
2629
                    ret += str;
2630
                else
2631
                    ret = str;
2632
2633
                /* we're done with the text */
2634
                break;
2635
            }
2636
        }
2637
2638
        /* return the output string */
2639
        return ret;
2640
    }
2641
2642
    /* our lookup table of defined aliases */
2643
    aliasTable = static new LookupTable()
2644
;
2645