cfad47cfa3/t3compiler/tads3/lib/adv3/misc.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library - miscellaneous definitions
7
 *   
8
 *   This module contains miscellaneous definitions that don't have a
9
 *   natural grouping with any larger modules, and which aren't complex
10
 *   enough to justify modules of their own.  
11
 */
12
13
/* include the library header */
14
#include "adv3.h"
15
16
17
/* ------------------------------------------------------------------------ */
18
/*
19
 *   When a call is made to a property not defined or inherited by the
20
 *   target object, the system will automatically invoke this method.  The
21
 *   method will be invoked with a property pointer as its first argument,
22
 *   and the original arguments as the remaining arguments.  The first
23
 *   argument gives the property that was invoked and not defined by the
24
 *   object.  A typical definition in an object would look like this:
25
 *   
26
 *   propNotDefined(prop, [args]) { ... }
27
 *   
28
 *   If this method is not defined by the object, the system simply
29
 *   returns nil as the value of the undefined property evaluation or
30
 *   method invocation.
31
 */
32
property propNotDefined;
33
export propNotDefined;
34
35
36
/* ------------------------------------------------------------------------ */
37
/*
38
 *   We refer to some properties defined primarily in score.t - that's an
39
 *   optional module, though, so make sure the compiler has heard of these. 
40
 */
41
property calcMaxScore, runScoreNotifier;
42
43
44
/* ------------------------------------------------------------------------ */
45
/*
46
 *   The library base class for the gameMain object.
47
 *   
48
 *   Each game MUST define an object called 'gameMain' to define how the
49
 *   game starts up.  You can use GameMainDef as the base class of your
50
 *   'gameMain' object, in which case the only thing you're required to
51
 *   specify in your object is the 'initialPlayerChar' property - you can
52
 *   inherit everything else from the GameMainDef class if you don't
53
 *   require any further customizations.  
54
 */
55
class GameMainDef: object
56
    /*
57
     *   The initial player character.  Each game's 'gameMain' object MUST
58
     *   define this to refer to the Actor object that serves as the
59
     *   initial player character. 
60
     */
61
    initialPlayerChar = nil
62
63
    /*
64
     *   Show the game's introduction.  This routine is called by the
65
     *   default newGame() just before entering the main command loop.  The
66
     *   command loop starts off by showing the initial room description,
67
     *   so there's no need to do that here.
68
     *   
69
     *   Most games will want to override this, to show a prologue message
70
     *   setting up the game's initial situation for the player.  We don't
71
     *   show anything by default.  
72
     */
73
    showIntro() { }
74
75
    /*
76
     *   Show the "goodbye" message.  This is called after the main command
77
     *   loop terminates.
78
     *   
79
     *   We don't show anything by default.  If you want to show a "thanks
80
     *   for playing" type of message as the game exits, override this
81
     *   routine with the desired text.  
82
     */
83
    showGoodbye() { }
84
85
    /*
86
     *   Begin a new game.  This default implementation shows the
87
     *   introductory message, calls the main command loop, and finally
88
     *   shows the goodbye message.
89
     *   
90
     *   You can override this routine if you want to customize the startup
91
     *   protocol.  For example, if you want to create a pre-game options
92
     *   menu, you could override this routine to show the list of options
93
     *   and process the user's input.  If you need only to customize the
94
     *   introduction and goodbye messages, you can simply override
95
     *   showIntro() and showGoodbye() instead.  
96
     */
97
    newGame()
98
    {
99
        /* try restoring the global defaults */
100
        settingsManager.restoreSettings();
101
102
        /* 
103
         *   Show the statusline before we display our introductory.  This
104
         *   will help minimize redrawing - if we waited until after
105
         *   displaying some text, we might have to redraw some of the
106
         *   screen to rearrange things for the new screen area taken up by
107
         *   the status line, which could be visible to the user.  By
108
         *   setting up the status line first, we'll probably have less to
109
         *   redraw because we won't have anything on the screen yet when
110
         *   figuring the layout.  
111
         */
112
        statusLine.showStatusLine();
113
114
        /* show the introduction */
115
        showIntro();
116
117
        /* run the game, showing the initial location's full description */
118
        runGame(true);
119
120
        /* show the end-of-game message */
121
        showGoodbye();
122
    }
123
124
    /*
125
     *   Restore a game and start it running.  This is invoked when the
126
     *   user launches the interpreter using a saved game file; for
127
     *   example, on a Macintosh, this happens when the user double-clicks
128
     *   on a saved game file on the desktop.
129
     *   
130
     *   This default implementation bypasses any normal introduction
131
     *   messages: we simply restore the game file if possible, and
132
     *   immediately start the game's main command loop.  Most games won't
133
     *   need to override this, but you can if you need some special effect
134
     *   in the restore-at-startup case.  
135
     */
136
    restoreAndRunGame(filename)
137
    {
138
        local succ;
139
140
        /* mention that we're about to restore the saved position */
141
        gLibMessages.noteMainRestore();
142
143
        /* try restoring it */
144
        succ = RestoreAction.startupRestore(filename);
145
146
        /* show a blank line after the restore result message */
147
        "<.p>";
148
149
        /* if we were successful, run the game */
150
        if (succ)
151
        {
152
            /* 
153
             *   Run the command loop.  There's no need to show the room
154
             *   description, since the RESTORE action will have already
155
             *   done so. 
156
             */
157
            runGame(nil);
158
159
            /* show the end-of-game message */
160
            showGoodbye();
161
        }
162
    }
163
164
    /*
165
     *   Set the interpreter window title, if applicable to the local
166
     *   platform.  This simply displays a <TITLE> tag to set the title to
167
     *   the string found in the versionInfo object.  
168
     */
169
    setGameTitle()
170
    {
171
        /* write the <TITLE> tag with the game's name */
172
        "<title><<versionInfo.name>></title>";
173
    }
174
175
    /*
176
     *   Set up the HTML-mode about-box.  By default, this does nothing.
177
     *   Games can use this routine to show an <ABOUTBOX> tag, if desired,
178
     *   to set up the contents of an about-box for HTML TADS platforms.
179
     *   
180
     *   Note that an <ABOUTBOX> tag must be re-initialized each time the
181
     *   main game window is cleared, so this routine should be called
182
     *   again after any call to clearScreen().  
183
     */
184
    setAboutBox()
185
    {
186
        /* we don't show any about-box by default */
187
    }
188
189
    /*
190
     *   The gameMain object also specifies some settings that control
191
     *   optional library behavior.  If you want the standard library
192
     *   behavior, you can just inherit the default settings from this
193
     *   class.  Some games might want to select non-default variations,
194
     *   though.  
195
     */
196
197
    /* 
198
     *   The maximum number of points possible in the game.  If the game
199
     *   includes the scoring module at all, and this is non-nil, the SCORE
200
     *   and FULL SCORE commands will display this value to the player as a
201
     *   rough indication of how much farther there is to go in the game.
202
     *   
203
     *   By default, we initialize this on demand, by calculating the sum
204
     *   of the point values of the Achievement objects in the game.  The
205
     *   game can override this if needed to specify a specific maximum
206
     *   possible score, rather than relying on the automatic calculation.
207
     */
208
    maxScore()
209
    {
210
        local m;
211
        
212
        /* ask the score module (if any) to compute the maximum score */
213
        m = (libGlobal.scoreObj != nil
214
             ? libGlobal.scoreObj.calcMaxScore : nil);
215
216
        /* supersede this initializer with the calculated value */
217
        maxScore = m;
218
219
        /* return the result */
220
        return m;
221
    }
222
223
    /*
224
     *   The score ranking list - this provides a list of names for
225
     *   various score levels.  If the game provides a non-nil list here,
226
     *   the SCORE and FULL SCORE commands will show the rank along with
227
     *   the score ("This makes you a Master Adventurer").
228
     *   
229
     *   This is a list of score entries.  Each score entry is itself a
230
     *   list of two elements: the first element is the minimum score for
231
     *   the rank, and the second is a string describing the rank.  The
232
     *   ranks should be given in ascending order, since we simply search
233
     *   the list for the first item whose minimum score is greater than
234
     *   our score, and use the preceding item.  The first entry in the
235
     *   list would normally have a minimum of zero points, since it
236
     *   should give the initial, lowest rank.
237
     *   
238
     *   If this is set to nil, which it is by default, we'll simply skip
239
     *   score ranks entirely.  
240
     */
241
    scoreRankTable = nil
242
243
    /* 
244
     *   Verbose mode.  If this is on, the full room description is
245
     *   displayed each time the player enters a room, regardless of
246
     *   whether or not the player has seen the room before; if this is
247
     *   nil, the full description is only displayed on the player's first
248
     *   entry to a room, and only the short description on re-entry.  Note
249
     *   that the library provides VERBOSE and TERSE commands that let the
250
     *   player change this setting dynamically.
251
     *   
252
     *   We use a BinarySettingsItem to store the current mode, so that
253
     *   this setting's default will be taken from the user's global
254
     *   cross-game preferences.  
255
     */
256
    verboseMode = verboseModeSettingsItem
257
258
    /*
259
     *   Option flag: allow the player to use "you" and "me"
260
     *   interchangeably in referring to the player character.  We set this
261
     *   true by default, so that the player can refer to the player
262
     *   character in either the first or second person, regardless of how
263
     *   the game refers to the PC.
264
     *   
265
     *   If desired, the game can set this flag to nil to force the player
266
     *   to use the correct pronoun to refer to the player character.  We
267
     *   define "correct" in the case of first or second person as the
268
     *   complement of what the game uses: if the game calls the PC "me",
269
     *   the player must say "you", and vice versa.  In a third-person
270
     *   game, the player must also refer to the PC in the third person.
271
     *   
272
     *   We set the default to allow using "you" and "me" interchangeably
273
     *   because (a) this will create no confusion in most games, and (b)
274
     *   many players would be annoyed otherwise.  For one thing, most
275
     *   experienced IF players will be rather set in their ways; they'll
276
     *   be accustomed to using either "me" or "you" (but usually "me") to
277
     *   refer to the PC, and will tend out of habit to do so even in games
278
     *   that don't use the traditional second-person narration format.
279
     *   For another thing, different players have different ideas about
280
     *   whether the PC is "you" or "me" in input, even in a conventional
281
     *   second-person game.  Some players think in terms of a conversation
282
     *   with the narrator, in which case the narrator's "you" is the
283
     *   player's "me", and vice versa; other players are rather more
284
     *   literal-minded, assuming that if the game talks about "you" then
285
     *   so should the player.
286
     *   
287
     *   Even in games that use first-person or third-person narration, it
288
     *   seems unlikely that there will be a separate second-person element
289
     *   to the narration, and as long as that's true, it should cause no
290
     *   confusion for the game to accept "you" and "me" as equivalent in
291
     *   commands.  However, the library provides this option in case such
292
     *   as situation does arise.  
293
     */
294
    allowYouMeMixing = true
295
296
    /*
297
     *   Option flag: filter plural phrase matches exclude the most obvious
298
     *   illogicalities, such as trying to TAKE an object that's already
299
     *   being held, or trying to OPEN an object that's already open.
300
     *   
301
     *   This is set to true by default, which means that we exclude an
302
     *   object from matching a plural phrase when the object's "verify"
303
     *   routine for the verb has an "illogical-already" or an
304
     *   "illogical-self" result.
305
     *   
306
     *   If you would prefer that plural words are simply matched to
307
     *   everything present that matches the vocabulary, without any
308
     *   filtering at all, override this and set it to nil.  
309
     */
310
    filterPluralMatches = true
311
312
    /*
313
     *   Option flag: allow ALL to be used for every verb.  This is true by
314
     *   default, which means that players will be allowed to use ALL with
315
     *   any command - OPEN ALL, EXAMINE ALL, etc.
316
     *   
317
     *   Some authors don't like to allow players to use ALL with so many
318
     *   verbs, because they think it's a sort of "cheating" when players
319
     *   try things like OPEN ALL.  This option lets you disable ALL for
320
     *   most verbs; if you set this to nil, only the basic inventory
321
     *   management verbs (TAKE, TAKE FROM, DROP, PUT IN, PUT ON) will
322
     *   allow ALL, and other verbs will simply respond with an error
323
     *   ("'All' isn't allowed with that verb").
324
     *   
325
     *   If you're writing an especially puzzle-oriented game, you might
326
     *   want to set this to nil.  It's a trade-off though, as some people
327
     *   will think your game is less player-friendly if you disable ALL.  
328
     */
329
    allVerbsAllowAll = true
330
331
    /*
332
     *   When a command fails, should we continue processing any remaining
333
     *   commands on the same command line, or simply ignore them?  The
334
     *   reason we might want to ignore additional commands is that they
335
     *   might not do what the player was expecting if an earlier command
336
     *   failed; this can sometimes create confusing situations, because
337
     *   the player expected one effect but got something quite different.
338
     *   On the other hand, *not* executing all the commands on the command
339
     *   line could be confusing in its own way, since the game's
340
     *   assessment of what constitutes "failure" might not be clear to the
341
     *   player; from the player's perspective, the game might appear to be
342
     *   inexplicably skipping commands.
343
     *   
344
     *   There's no perfect solution.  As always, the ideal is to
345
     *   understand the player's intentions and act accordingly.  But when
346
     *   a command fails, it's usually because the player's idea of what's
347
     *   going on is out of sync with the game's - in other words, if we're
348
     *   in this situation to start with, it's probably because our best
349
     *   effort to understand the player's intentions has already failed.
350
     *   This isn't always the case; sometimes we understand the player's
351
     *   intentions perfectly well, but the command fails anyway because of
352
     *   some surprising new development.  In these cases, aborting the
353
     *   rest of the command is arguably the right approach, because the
354
     *   player will need a chance to reconsider the pre-typed commands in
355
     *   light of the new information.  In other cases, though, it's not so
356
     *   clear.  For many players, the prime virtue for the parser is to be
357
     *   predictable, and the most predictable thing to do is to simply
358
     *   plow through the rest of the command line in all cases.
359
     *   
360
     *   Our traditional approach (from the early adv3 versions, and even
361
     *   in tads 2) has been the simple-minded approach - just keep going
362
     *   in all cases.  So, we make this the default.  You can abort
363
     *   remaining commands on a command failure by setting this to true.  
364
     */
365
    cancelCmdLineOnFailure = nil
366
367
    /*
368
     *   Should we use distinguishers when generating action object
369
     *   announcement messages?  If this is true, we'll be as specific as
370
     *   possible when listing default objects, vaguely matched objects,
371
     *   and multiple objects in action reports.  By default, this is nil,
372
     *   which means that we just use the object's base name in these
373
     *   announcements.
374
     *   
375
     *   (This is optional because the current implementation is a bit
376
     *   verbose for most people's taste.  The problem is that it's a bit
377
     *   *too* specific in many cases, showing more qualification than is
378
     *   really necessary.  We make this an option so that authors can
379
     *   enable it and possibly tweak it a bit to meet their needs.)  
380
     */
381
    useDistinguishersInAnnouncements = nil
382
383
    /*
384
     *   How should we handle object announcements when an object is
385
     *   automatically disambiguated?  This controls how an action is
386
     *   described when the parser uses the logicalness rules to narrow
387
     *   down the object for a noun phrase when the noun phrase could refer
388
     *   to multiple in-scope objects.  There are three options:
389
     *   
390
     *   AnnounceUnclear - Make a parenthetical announcement only when the
391
     *   choice is *not* clear (as described below).  This is the original
392
     *   library behavior, from before this option was added.
393
     *   
394
     *   AnnounceClear - Make a parenthetical announcement (for example,
395
     *   "(the red door)") for all disambiguated objects, whether clear or
396
     *   unclear.  We don't make an announcement when there's only one
397
     *   in-scope object matching the noun phrase - the announcement is
398
     *   only when multiple objects match the words.
399
     *   
400
     *   DescribeClear - For *unclear* disambiguation, make a parenthetical
401
     *   announcement, to emphasize that the parser had to make a choice.
402
     *   For *clear* disambiguation, skip the announcement, but *do* use a
403
     *   verbose version of the library message in place of one of the
404
     *   terse default replies.  For example, for >TAKE BOX, instead of
405
     *   "Taken", we would reply "You take the green box."  The longer
406
     *   reply in these cases always mentions the involved object by name,
407
     *   to make it clear exactly which object we chose to use.
408
     *   
409
     *   The default setting is DescribeClear.
410
     *   
411
     *   This only applies when the disambiguation choice is clear - that
412
     *   is, when there's exactly one in-scope object that passes the
413
     *   logicalness tests.  For example, if the current location contains
414
     *   a red door that's open and a green door that's closed, CLOSE DOOR
415
     *   clearly refers to the red door because the other one is already
416
     *   closed - it's not logical.  There are other cases where the
417
     *   disambiguation is a best guess rather than a clear choice, such as
418
     *   when there are multiple logical objects but there's one that's
419
     *   more likely than the others due to the logicalRank results.  In
420
     *   those best-guess situations, the parser always announces its
421
     *   decision, because it's entirely plausible that the player meant
422
     *   one of the other logical, but less likely, choices.  
423
     */
424
    ambigAnnounceMode = DescribeClear
425
426
    /*
427
     *   Should the "before" notifications (beforeAction, roomBeforeAction,
428
     *   and actorAction) run before or after the "check" phase?
429
     *   
430
     *   The library traditionally ran the "before" notifiers first, so
431
     *   this is the default.  However, in many ways it's more logical and
432
     *   useful to run "check" first.  That way, you can consider the
433
     *   action to be more or less committed by the time the "before"
434
     *   notifiers are invoked.  Of course, a command is never *truly*
435
     *   committed until it's actually been executed, since a "before"
436
     *   handler could always cancel it.  But this is relatively rare -
437
     *   "before" handlers usually carry out side effects, so it's very
438
     *   useful to be able to know that the command has already passed all
439
     *   of its own internal checks by the time "before" is invoked - that
440
     *   way, you can invoke side effects without worrying that the command
441
     *   will subsequently fail.  
442
     */
443
    beforeRunsBeforeCheck = true
444
;
445
446
/*
447
 *   The VERBOSE mode settings item. 
448
 */
449
verboseModeSettingsItem: BinarySettingsItem
450
    /* VERBOSE mode is on by default */
451
    isOn = true
452
453
    /* our configuration file variable ID */
454
    settingID = 'adv3.verbose'
455
456
    /* show our description */
457
    settingDesc = (gLibMessages.shortVerboseStatus(isOn))
458
;
459
460
/* ------------------------------------------------------------------------ */
461
/*
462
 *   Clear the main game window.  In most cases, you should call this
463
 *   rather than calling the low-level clearScreen() function directly,
464
 *   since this routine takes care of a couple of chores that should
465
 *   usually be done at the same time.
466
 *   
467
 *   First, we flush the transcript to ensure that no left-over reports
468
 *   that were displayed before we cleared the screen will show up on the
469
 *   new screen.  Second, we call the low-level clearScreen() function to
470
 *   actually clear the display window.  Finally, we re-display any
471
 *   <ABOUTBOX> tag, to ensure that the about-box will still be around;
472
 *   this is necessary because any existing <ABOUTBOX> tag is lost after
473
 *   the screen is cleared.  
474
 */
475
cls()
476
{
477
    /* flush any captured transcript output */
478
    if (gTranscript != nil)
479
        gTranscript.flushForInput();
480
481
    /* clear the screen */
482
    clearScreen();
483
484
    /* re-initialize any <ABOUTBOX> tag */
485
    gameMain.setAboutBox();
486
}
487
488
/* ------------------------------------------------------------------------ */
489
/*
490
 *   Run the game.  We start by showing the description of the initial
491
 *   location, if desired, and then we read and interpret commands until
492
 *   the game ends (via a "quit" command, winning, death of the player
493
 *   character, or any other way of terminating the game).
494
 *   
495
 *   This routine doesn't return until the game ends.
496
 *   
497
 *   Before calling this routine, the caller should already have set the
498
 *   global variable gPlayerChar to the player character actor.
499
 *   
500
 *   'look' is a flag indicating whether or not to look around; if this is
501
 *   true, we'll show a full description of the player character's initial
502
 *   location, as though the player were to type "look around" as the first
503
 *   command.  
504
 */
505
runGame(look)
506
{
507
    /* show the starting location */
508
    if (look)
509
    {
510
        /* run the initial "look around" in a dummy command context */
511
        withActionEnv(EventAction, gPlayerChar,
512
                      {: gPlayerChar.lookAround(true) });
513
    }
514
515
    /* run the scheduling loop until the game ends */
516
    runScheduler();
517
}
518
519
/* ------------------------------------------------------------------------ */
520
/*
521
 *   Main program entrypoint.  The core run-time start-up code calls this
522
 *   after running pre-initialization and load-time initialization.  This
523
 *   entrypoint is called when we're starting the game normally; when the
524
 *   game is launched through a saved-position file, mainRestore() will be
525
 *   invoked instead.  
526
 */
527
main(args)
528
{
529
    try
530
    {
531
        /* initialize the display */
532
        initDisplay();
533
534
        /* call the gameMain object to set up a new game */
535
        gameMain.newGame();
536
    }
537
    catch (QuittingException q)
538
    {
539
        /* 
540
         *   ignore this exception - it's just a signal to quit the game,
541
         *   which we will now proceed to do by returning from this
542
         *   function, which exits the program 
543
         */
544
    }
545
}
546
547
/*
548
 *   Initialize the display.  We call this when we first enter the
549
 *   interpreter to set up the main game window.  
550
 */
551
initDisplay()
552
{
553
    /* set the interpreter window title */
554
    gameMain.setGameTitle();
555
556
    /* set up the ABOUT box */
557
    gameMain.setAboutBox();
558
}
559
560
/*
561
 *   Main program entrypoint for restoring a saved-position file.  This is
562
 *   invoked from the core run-time start-up code when the game is launched
563
 *   from the operating system via a saved-position file.  For example, on
564
 *   Windows, double-clicking on a saved-position file on the Windows
565
 *   desktop launches the interpreter, which looks in the save file to find
566
 *   the game executable to run, then starts the game and invokes this
567
 *   entrypoint.  
568
 */
569
mainRestore(args, restoreFile)
570
{
571
    try
572
    {
573
        /* initialize the display */
574
        initDisplay();
575
576
        /* call the gameMain object to restore the specified saved game */
577
        gameMain.restoreAndRunGame(restoreFile);
578
    }
579
    catch (QuittingException q)
580
    {
581
        /* 
582
         *   ignore this exception - it's just a signal to quit the game,
583
         *   which we will now proceed to do by returning from this
584
         *   function, which exits the program 
585
         */
586
    }
587
}
588
589
/* ------------------------------------------------------------------------ */
590
/*
591
 *   Determine if the given object overrides the definition of the given
592
 *   property inherited from the given base class.  Returns true if the
593
 *   object derives from the given base class, and the object's definition
594
 *   of the property comes from a different place than the base class's
595
 *   definition of the property.  
596
 */
597
overrides(obj, base, prop)
598
{
599
    return (obj.ofKind(base)
600
            && (obj.propDefined(prop, PropDefGetClass)
601
                != base.propDefined(prop, PropDefGetClass)));
602
}
603
604
/* ------------------------------------------------------------------------ */
605
/*
606
 *   Library Pre-Initializer.  This object performs the following
607
 *   initialization operations immediately after compilation is completed:
608
 *   
609
 *   - adds each defined Thing to its container's contents list
610
 *   
611
 *   - adds each defined Sense to the global sense list
612
 *   
613
 *   This object is named so that other libraries and/or user code can
614
 *   create initialization order dependencies upon it.  
615
 */
616
adv3LibPreinit: PreinitObject
617
    execute()
618
    {
619
        /* save each SettingsItem's factory default settings */
620
        forEachInstance(SettingsItem,
621
                        {i: i.factoryDefault = i.settingToText()});
622
623
        /* set the initial player character, as specified in gameMain */
624
        gPlayerChar = gameMain.initialPlayerChar;
625
        
626
        /* 
627
         *   visit every VocabObject, and run its vocabulary initializer
628
         *   (this routine will be defined in the language-specific part
629
         *   of the library to enter each object's vocabulary words into
630
         *   the dictionary) 
631
         */
632
        forEachInstance(VocabObject, { obj: obj.initializeVocab() });
633
634
        /* visit every Thing, and run its general initializer */
635
        forEachInstance(Thing, { obj: obj.initializeThing() });
636
637
        /* initialize SpecialTopic objects */
638
        forEachInstance(SpecialTopic, { obj: obj.initializeSpecialTopic() });
639
640
        /* 
641
         *   Initialize each MultiInstance object.  Do this after
642
         *   initializing the Thing objects, because we'll be dynamically
643
         *   constructing new Thing objects for the instances.  Those new
644
         *   Things will be initialized by their constructors, so we don't
645
         *   want to initialize them redundantly with explicit
646
         *   initializeThing calls.  
647
         */
648
        forEachInstance(MultiInstance, { obj: obj.initializeLocation() });
649
650
        /* add every Sense to the global sense list */
651
        forEachInstance(Sense, { obj: libGlobal.allSenses += obj });
652
653
        /* 
654
         *   initialize each ActorState - do this before initializing
655
         *   actors, since we want each actor's initial state to plug
656
         *   itself into its actor before we initialize the actors 
657
         */
658
        forEachInstance(ActorState, { obj: obj.initializeActorState() });
659
660
        /* initialize each Actor */
661
        forEachInstance(Actor, { obj: obj.initializeActor() });
662
663
        /* 
664
         *   initialize the AltTopics first, to set up their parents' lists
665
         *   of their AltTopic children 
666
         */
667
        forEachInstance(AltTopic, { obj: obj.initializeAltTopic() });
668
669
        /* initialize the topic database entries */
670
        forEachInstance(TopicEntry, { obj: obj.initializeTopicEntry() });
671
672
        /* initialize the suggested topics */
673
        forEachInstance(SuggestedTopic,
674
            { obj: obj.initializeSuggestedTopic() });
675
676
        /* initialize the master direction list */
677
        Direction.initializeDirectionClass();
678
679
        /* initialize the noise/odor notification daemon */
680
        local d = new Daemon(SensoryEmanation, &noteSenseChanges, 1);
681
682
        /* 
683
         *   give it a later-than-default event order, so that it runs
684
         *   after most other daemons and fuses 
685
         */
686
        d.eventOrder = 500;
687
688
        /* set up a daemon for the current location */
689
        new Daemon(BasicLocation, &dispatchRoomDaemon, 1);
690
691
        /* 
692
         *   Initialize the status line daemon.  Set this daemon's event
693
         *   order to a high value so that it runs last, after all other
694
         *   daemons - we want this to be the last prompt daemon so that
695
         *   the status line is updated after any other daemons have done
696
         *   their jobs already, in case any of them move the player
697
         *   character to a new location or affect the score, or make any
698
         *   other changes that should be reflected on the status line.  
699
         */
700
        local sld = new PromptDaemon(statusLine, &showStatusLineDaemon);
701
        sld.eventOrder = 1000;
702
703
        /* 
704
         *   Attach the command sequencer output filter, the
705
         *   language-specific message parameter substitution filter, the
706
         *   style tag formatter filter, and the paragraph filter to the
707
         *   main output stream.  Stack them so that the paragraph manager
708
         *   is at the bottom, since the library tag filter can produce
709
         *   paragraph tags and thus needs to sit atop the paragraph
710
         *   filter.  Put the command sequencer above those, since it
711
         *   might need to write style tags.  Finally, put the sense
712
         *   context filter on top of those.  
713
         */
714
        mainOutputStream.addOutputFilter(typographicalOutputFilter);
715
        mainOutputStream.addOutputFilter(mainParagraphManager);
716
        mainOutputStream.addOutputFilter(styleTagFilter);
717
        mainOutputStream.addOutputFilter(langMessageBuilder);
718
        mainOutputStream.addOutputFilter(commandSequencer);
719
        mainOutputStream.addOutputFilter(conversationManager);
720
        mainOutputStream.addOutputFilter(senseContext);
721
722
        /* 
723
         *   Attach our message parameter filter and style tag filter to
724
         *   the status line streams.  We don't need most of the main
725
         *   window's filters in the status line.  
726
         */
727
        statusTagOutputStream.addOutputFilter(styleTagFilter);
728
        statusTagOutputStream.addOutputFilter(langMessageBuilder);
729
730
        statusLeftOutputStream.addOutputFilter(styleTagFilter);
731
        statusLeftOutputStream.addOutputFilter(langMessageBuilder);
732
733
        statusRightOutputStream.addOutputFilter(styleTagFilter);
734
        statusRightOutputStream.addOutputFilter(langMessageBuilder);
735
    }
736
737
    /* 
738
     *   Make sure the output streams we depend on are initialized before
739
     *   me (so that they set up properly internally).  Also, make sure
740
     *   that the message builder object (langMessageBuilder) is set up
741
     *   first, so that we can add entries to its parameter substitution
742
     *   table.  
743
     */
744
    execBeforeMe = [mainOutputStream, statusTagOutputStream,
745
                    statusLeftOutputStream, statusRightOutputStream,
746
                    langMessageBuilder]
747
;
748
749
/* ------------------------------------------------------------------------ */
750
/*
751
 *   Library Initializer.  This object performs the following
752
 *   initialization operations each time the game is started:
753
 *   
754
 *   - sets up the library's default output function 
755
 */
756
adv3LibInit: InitObject
757
    execute()
758
    {
759
        /* 
760
         *   Set up our default output function.  Note that we must do
761
         *   this during run-time initialization each time we start the
762
         *   game, rather than during pre-initialization, because the
763
         *   default output function state is not part of the load-image
764
         *   configuration. 
765
         */
766
        t3SetSay(say);
767
    }
768
;
769
770
771
/* ------------------------------------------------------------------------ */
772
/*
773
 *   Generic script object.  This class can be used to implement a simple
774
 *   state machine.  
775
 */
776
class Script: object
777
    /* 
778
     *   Get the current state.  This returns a value that gives the
779
     *   current state of the script, which is usually simply an integer.  
780
     */
781
    getScriptState()
782
    {
783
        /* by default, return our state property */
784
        return curScriptState;
785
    }
786
787
    /*
788
     *   Process the next step of the script.  This routine must be
789
     *   overridden to perform the action of the script.  This routine's
790
     *   action should call getScriptState() to get our current state, and
791
     *   should update the internal state appropriately to take us to the
792
     *   next step after the current one.
793
     *   
794
     *   By default, we don't do anything at all.  
795
     */
796
    doScript()
797
    {
798
        /* override to carry out the script */
799
    }
800
801
    /* 
802
     *   Property giving our current state.  This should never be used
803
     *   directly; instead, getScriptState() should always be used, since
804
     *   getScriptState() can be overridden so that the state depends on
805
     *   something other than this internal state property. The meaning of
806
     *   the state identifier is specific to each subclass.  
807
     */
808
    curScriptState = 0
809
;
810
811
/*
812
 *   Random-Firing script add-in.  This is a mix-in class that you can add
813
 *   to the superclass list of any Script subclass to make the script
814
 *   execute only a given percentage of the time it's invoked.  Each time
815
 *   doScript() is invoked on the script, we'll look at the probability
816
 *   settings (see the properties below) to determine whether we really
817
 *   want to execute the script this time; if so, we'll proceed with the
818
 *   scripted event, otherwise we'll just return immediately, doing
819
 *   nothing.
820
 *   
821
 *   Note that this must be used in the superclass list *before* the Script
822
 *   subclass:
823
 *   
824
 *   myScript: RandomFiringScript, EventList
825
 *.    // ...my definitions...
826
 *.  ;
827
 *   
828
 *   This class is especially useful for random atmospheric events, because
829
 *   it allows you to make the timing of scripted events random.  Rather
830
 *   than making a scripted event happen on every single turn, you can use
831
 *   this to make events happen only sporadically.  It can often feel too
832
 *   predictable and repetitious when a random background event happens on
833
 *   every single turn; firing events less frequently often makes them feel
834
 *   more realistic.  
835
 */
836
class RandomFiringScript: object
837
    /* 
838
     *   Percentage of the time an event occurs.  By default, we execute an
839
     *   event 100% of the time - meaning every time that doScript() is
840
     *   invoked.  If you set this to a lower percentage, then each time
841
     *   doScript() is invoked, we'll randomly decide whether or not to
842
     *   execute an event based on this percentage.  For example, if you
843
     *   want an event to execute on average about a third of the time, set
844
     *   this to 33.
845
     *   
846
     *   Note that this is a probabilistic frequency.  Setting this to 33
847
     *   does *not* mean that we'll execute exactly every third time.
848
     *   Rather, it means that we'll randomly execute or not on each
849
     *   invocation, and averaged over a large number of invocations, we'll
850
     *   execute about a third of the time.  
851
     */
852
    eventPercent = 100
853
854
    /* 
855
     *   Random atmospheric events can get repetitive after a while, so we
856
     *   provide an easy way to reduce the frequency of our events after a
857
     *   while.  This way, we'll generate the events more frequently at
858
     *   first, but once the player has seen them enough to get the idea,
859
     *   we'll cut back.  Sometimes, the player will spend a lot of time in
860
     *   one place trying to solve a puzzle, so the same set of random
861
     *   events can get stale.  Set eventReduceAfter to the number of times
862
     *   you want the events to be generated at full frequency; after we've
863
     *   fired events that many times, we'll change eventPercent to
864
     *   eventReduceTo.  If eventReduceAfter is nil, we won't ever change
865
     *   eventPercent.  
866
     */
867
    eventReduceAfter = nil
868
    eventReduceTo = nil
869
870
    /*
871
     *   When doScript() is invoked, check the event probabilities before
872
     *   proceeding.  
873
     */
874
    doScript()
875
    {
876
        /* process the script step only if the event odds allow it */
877
        if (checkEventOdds())
878
            inherited();
879
    }
880
881
    /*
882
     *   Check the event odds to see if we want to fire an event at all on
883
     *   this invocation.  
884
     */
885
    checkEventOdds()
886
    {
887
        /* 
888
         *   check the event odds to see if we fire an event this time; if
889
         *   not, we're done with the script invocation 
890
         */
891
        if (rand(100) >= eventPercent)
892
            return nil;
893
894
        /* 
895
         *   we're firing an event this time, so count this against the
896
         *   reduction limit, if there is one 
897
         */
898
        if (eventReduceAfter != nil)
899
        {
900
            /* decrement the limit counter */
901
            --eventReduceAfter;
902
            
903
            /* if it has reached zero, apply the reduced frequency */
904
            if (eventReduceAfter == 0)
905
            {
906
                /* apply the reduced frequency */
907
                eventPercent = eventReduceTo;
908
                
909
                /* we no longer have a limit to look for */
910
                eventReduceAfter = nil;
911
            }
912
        }
913
914
        /* indicate that we do want to fire an event */
915
        return true;
916
    }
917
;
918
919
/* ------------------------------------------------------------------------ */
920
/*
921
 *   An "event list."  This is a general-purpose type of script that lets
922
 *   you define the scripted events separately from the Script object.
923
 *   
924
 *   The script is driven by a list of values; each value represents one
925
 *   step of the script.  Each value can be a single-quoted string, in
926
 *   which case the string is simply displayed; a function pointer, in
927
 *   which case the function is invoked without arguments; another Script
928
 *   object, in which case the object's doScript() method is invoked; a
929
 *   property pointer, in which case the property of 'self' (the EventList
930
 *   object) is invoked with no arguments; or nil, in which case nothing
931
 *   happens.
932
 *   
933
 *   This base type of event list runs through the list once, in order, and
934
 *   then simply stops doing anything once we pass the last event.  
935
 */
936
class EventList: Script
937
    construct(lst) { eventList = lst; }
938
939
    /* the list of events */
940
    eventList = []
941
942
    /* advance to the next state */
943
    advanceState()
944
    {
945
        /* increment our state index */
946
        ++curScriptState;
947
    }
948
949
    /* by default, start at the first list element */
950
    curScriptState = 1
951
952
    /* process the next step of the script */
953
    doScript()
954
    {
955
        local idx;
956
        
957
        /* get our current event state */
958
        idx = getScriptState();
959
960
        /* if it's a valid index in our list, fire the event */
961
        if (idx >= 1 && idx <= eventList.length())
962
        {
963
            /* carry out the event */
964
            doScriptEvent(eventList[idx]);
965
        }
966
967
        /* perform any end-of-script processing */
968
        scriptDone();
969
    }
970
971
    /* carry out one script event */
972
    doScriptEvent(evt)
973
    {
974
        /* check what kind of event we have */
975
        switch (dataTypeXlat(evt))
976
        {
977
        case TypeSString:
978
            /* it's a string - display it */
979
            say(evt);
980
            break;
981
            
982
        case TypeObject:
983
            /* it must be a Script object - invoke its doScript() method */
984
            evt.doScript();
985
            break;
986
            
987
        case TypeFuncPtr:
988
            /* it's a function pointer - invoke it */
989
            (evt)();
990
            break;
991
            
992
        case TypeProp:
993
            /* it's a property of self - invoke it */
994
            self.(evt)();
995
            break;
996
            
997
        default:
998
            /* do nothing in other cases */
999
            break;
1000
        }
1001
    }
1002
1003
    /*
1004
     *   Perform any end-of-script processing.  By default, we advance the
1005
     *   script to the next state.
1006
     *   
1007
     *   Some scripts might want to override this.  For example, a script
1008
     *   could be driven entirely by some external timing; the state of a
1009
     *   script could vary once per turn, for example, or could change each
1010
     *   time an actor pushes a button.  In these cases, invoking the
1011
     *   script wouldn't affect the state of the event list, so the
1012
     *   subclass would override scriptDone() so that it does nothing at
1013
     *   all.  
1014
     */
1015
    scriptDone()
1016
    {
1017
        /* advance to the next state */
1018
        advanceState();
1019
    }
1020
;
1021
1022
/*
1023
 *   An "external" event list is one whose state is driven externally to
1024
 *   the script.  Specifically, the state is *not* advanced by invoking the
1025
 *   script; the state is advanced exclusively by some external process
1026
 *   (for example, by a daemon that invokes the event list's advanceState()
1027
 *   method).  
1028
 */
1029
class ExternalEventList: EventList
1030
    scriptDone() { }
1031
;
1032
1033
/*
1034
 *   A cyclical event list - this runs through the event list in order,
1035
 *   returning to the first element when we pass the last element.  
1036
 */
1037
class CyclicEventList: EventList
1038
    advanceState()
1039
    {
1040
        /* go to the next state */
1041
        ++curScriptState;
1042
1043
        /* if we've passed the end of the list, loop back to the start */
1044
        if (curScriptState > eventList.length())
1045
            curScriptState = 1;
1046
    }
1047
;
1048
1049
/*
1050
 *   A stopping event list - this runs through the event list in order,
1051
 *   then stops at the last item and repeats it each time the script is
1052
 *   subsequently invoked. 
1053
 *   
1054
 *   This is often useful for things like ASK ABOUT topics, where we reveal
1055
 *   more information when asked repeatedly about a topic, but eventually
1056
 *   reach a point where we've said everything:
1057
 *   
1058
 *.  >ask bob about black book
1059
 *.  "What makes you think I know anything about it?" he says, his
1060
 *   voice shaking.
1061
 *   
1062
 *   >again
1063
 *.  "No! You can't make me tell you!"
1064
 *   
1065
 *   >again
1066
 *.  "All right, I'll tell you what you want to know!  But I warn you,
1067
 *   these are things mortal men were never meant to know.  Your life, your
1068
 *   very soul will be in danger from the moment you hear these dark secrets!"
1069
 *   
1070
 *   >again
1071
 *.  [scene missing]
1072
 *   
1073
 *   >again
1074
 *.  "I've already told you all I know."
1075
 *   
1076
 *   >again
1077
 *.  "I've already told you all I know."
1078
 */
1079
class StopEventList: EventList
1080
    advanceState()
1081
    {
1082
        /* if we haven't yet reached the last state, go to the next one */
1083
        if (curScriptState < eventList.length())
1084
            ++curScriptState;
1085
    }
1086
;
1087
1088
/*
1089
 *   A synchronized event list.  This is an event list that takes its
1090
 *   actions from a separate event list object.  We get our current state
1091
 *   from the other list, and advancing our state advances the other list's
1092
 *   state in lock step.  Set 'masterObject' to refer to the master list
1093
 *   whose state we synchronize with.  
1094
 *   
1095
 *   This can be useful, for example, when we have messages that reflect
1096
 *   two different points of view on the same events: the messages for each
1097
 *   point of view can be kept in a separate list, but the one list can be
1098
 *   a slave of the other to ensure that the two lists are based on a
1099
 *   common state.  
1100
 */
1101
class SyncEventList: EventList
1102
    /* my master event list object */
1103
    masterObject = nil
1104
1105
    /* my state is simply the master list's state */
1106
    getScriptState() { return masterObject.getScriptState(); }
1107
1108
    /* to advance my state, advance the master list's state */
1109
    advanceState() { masterObject.advanceState(); }
1110
1111
    /* let the master list take care of finishing a script step */
1112
    scriptDone() { masterObject.scriptDone(); }
1113
;
1114
1115
/*
1116
 *   Randomized event list.  This is similar to a regular event list, but
1117
 *   chooses an event at random each time it's invoked.
1118
 */
1119
class RandomEventList: RandomFiringScript, EventList
1120
    /* process the next step of the script */
1121
    doScript()
1122
    {
1123
        local idx;
1124
1125
        /* check the odds to see if we want to fire an event at all */
1126
        if (!checkEventOdds())
1127
            return;
1128
        
1129
        /* get our next random number */
1130
        idx = getNextRandom();
1131
        
1132
        /* run the event, if the index is valid */
1133
        if (idx >= 1 && idx <= eventList.length())
1134
            doScriptEvent(eventList[idx]);
1135
    }
1136
    
1137
    /*
1138
     *   Get the next random state.  By default, we simply return a number
1139
     *   from 1 to the number of entries in our event list.  This is a
1140
     *   separate method to allow subclasses to customize the way the
1141
     *   random number is selected.  
1142
     */
1143
    getNextRandom()
1144
    {
1145
        /*   
1146
         *   Note that rand(n) returns a number from 0 to n-1 inclusive;
1147
         *   since list indices run from 1 to list.length, add one to the
1148
         *   result of rand(list.length) to get a value in the proper range
1149
         *   for a list index.  
1150
         */
1151
        return rand(eventList.length()) + 1;
1152
    }
1153
;
1154
1155
/*
1156
 *   Shuffled event list.  This is similar to a random event list, except
1157
 *   that we fire our events in a "shuffled" order rather than an
1158
 *   independently random order.  "Shuffled order" means that we fire the
1159
 *   events in random order, but we don't re-fire an event until we've run
1160
 *   through all of the other events.  The effect is as though we were
1161
 *   dealing from a deck of cards.
1162
 *   
1163
 *   For the first time through the main list, we normally shuffle the
1164
 *   strings immediately at startup, but this is optional.  If shuffleFirst
1165
 *   is set to nil, we will NOT shuffle the list the first time through -
1166
 *   we'll run through it once in the given order, then shuffle for the
1167
 *   next time through, then shuffle again for the next, and so on.  So, if
1168
 *   you want a specific order for the first time through, just define the
1169
 *   list in the desired order and set shuffleFirst to nil.
1170
 *   
1171
 *   You can optionally specify a separate list of one-time-only sequential
1172
 *   strings in the property firstEvents.  We'll run through these strings
1173
 *   once.  When we've exhausted them, we'll switch to the main eventList
1174
 *   list, showing it one time through in its given order, then shuffling
1175
 *   it and running through it again, and so on.  The firstEvents list is
1176
 *   never shuffled - it's always shown in exactly the order given.  
1177
 */
1178
class ShuffledEventList: RandomFiringScript, EventList
1179
    /* 
1180
     *   a list of events to go through sequentially, in the exact order
1181
     *   specified, before firing any events from the main list
1182
     */
1183
    firstEvents = []
1184
1185
    /*
1186
     *   Flag: shuffle the eventList list before we show it for the first
1187
     *   time.  By default, this is set to true, so that the behavior is
1188
     *   random on each independent run of the game.  However, it might be
1189
     *   desirable in some cases to always use the original ordering of the
1190
     *   eventList list the first time through the list.  If this is set to
1191
     *   nil, we won't shuffle the list the first time through.  
1192
     */
1193
    shuffleFirst = true
1194
1195
    /*
1196
     *   Flag: suppress repeats in the shuffle.  If this is true, it
1197
     *   prevents a given event from showing up twice in a row, which could
1198
     *   otherwise happen right after a shuffle.  This is ignored for lists
1199
     *   with one or two events: it's impossible to prevent repeats in a
1200
     *   one-element list, and doing so in a two-element list would produce
1201
     *   a predictable A-B-A-B... pattern.
1202
     *   
1203
     *   You might want to set this to nil for lists of three or four
1204
     *   elements, since such short lists can result in fairly
1205
     *   un-random-looking sequences when repeats are suppressed, because
1206
     *   the available number of permutations drops significantly.  
1207
     */
1208
    suppressRepeats = true
1209
1210
    /* process the next step of the script */
1211
    doScript()
1212
    {
1213
        local evt;
1214
        local firstLen = firstEvents.length();
1215
        local eventLen = eventList.length();
1216
1217
        /* process the script step only if the event odds allow it */
1218
        if (!checkEventOdds())
1219
            return;
1220
1221
        /* 
1222
         *   States 1..N, where N is the number of elements in the
1223
         *   firstEvents list, simply show the firstEvents elements in
1224
         *   order.
1225
         *   
1226
         *   If we're set to shuffle the main eventList list initially, all
1227
         *   states above N simply show elements from the eventList list in
1228
         *   shuffled order.
1229
         *   
1230
         *   If we're NOT set to shuffle the main eventList list initially,
1231
         *   the following apply:
1232
         *   
1233
         *   States N+1..N+M, where M is the number of elements in the
1234
         *   eventList list, show the eventList elements in order.
1235
         *   
1236
         *   States above N+M show elements from the eventList list in
1237
         *   shuffled order.  
1238
         */
1239
        if (curScriptState <= firstLen)
1240
        {
1241
            /* simply fetch the next string from firstEvents */
1242
            evt = firstEvents[curScriptState++];
1243
        }
1244
        else if (!shuffleFirst && curScriptState <= firstLen + eventLen)
1245
        {
1246
            /* fetch the next string from eventList */
1247
            evt = eventList[curScriptState++ - firstLen];
1248
        }
1249
        else
1250
        {
1251
            /* we're showing shuffled strings from the eventList list */
1252
            evt = eventList[getNextRandom()];
1253
        }
1254
1255
        /* execute the event */
1256
        doScriptEvent(evt);
1257
    }
1258
1259
1260
    /*
1261
     *   Get the next random event.  We'll pick an event from our list of
1262
     *   events using a ShuffledIntegerList to ensure we pick each value
1263
     *   once before re-using any values.  
1264
     */
1265
    getNextRandom()
1266
    {
1267
        /* if we haven't created our shuffled list yet, do so now */
1268
        if (shuffledList_ == nil)
1269
        {
1270
            /* 
1271
             *   create a shuffled integer list - we'll use these shuffled
1272
             *   integers as indices into our event list 
1273
             */
1274
            shuffledList_ = new ShuffledIntegerList(1, eventList.length());
1275
1276
            /* apply our suppressRepeats option to the shuffled list */
1277
            shuffledList_.suppressRepeats = suppressRepeats;
1278
        }
1279
1280
        /* ask the shuffled list to pick an element */
1281
        return shuffledList_.getNextValue();
1282
    }
1283
1284
    /* our ShuffledList - we'll initialize this on demand */
1285
    shuffledList_ = nil
1286
;
1287
1288
/* ------------------------------------------------------------------------ */
1289
/*
1290
 *   Shuffled List - this class keeps a list of values that can be returned
1291
 *   in random order, but with the constraint that we never repeat a value
1292
 *   until we've handed out every value.  Think of a shuffled deck of
1293
 *   cards: the order of the cards handed out is random, but once a card is
1294
 *   dealt, it can't be dealt again until we put everything back into the
1295
 *   deck and reshuffle.  
1296
 */
1297
class ShuffledList: object
1298
    /* 
1299
     *   the list of values we want to shuffle - initialize this in each
1300
     *   instance to the set of values we want to return in random order 
1301
     */
1302
    valueList = []
1303
1304
    /*
1305
     *   Flag: suppress repeated values.  We mostly suppress repeats by our
1306
     *   very design, since we run through the entire list before repeating
1307
     *   anything in the list.  However, there's one situation (in a list
1308
     *   with more than one element) where a repeat can occur: immediately
1309
     *   after a shuffle, we could select the last element from the
1310
     *   previous shuffle as the first element of the new shuffle.  If this
1311
     *   flag is set, we'll suppress this type of repeat by choosing again
1312
     *   any time we're about to choose a repeat.
1313
     *   
1314
     *   Note that we ignore this for a list of one element, since it's
1315
     *   obviously impossible to avoid repeats in this case.  We also
1316
     *   ignore it for a two-element list, since this would produce the
1317
     *   predictable pattern A-B-A-B..., defeating the purpose of the
1318
     *   shuffle.  
1319
     */
1320
    suppressRepeats = nil
1321
1322
    /* create from a given list */
1323
    construct(lst)
1324
    {
1325
        /* remember our list of values */
1326
        valueList = lst;
1327
    }
1328
1329
    /* 
1330
     *   Get a random value.  This will return a randomly-selected element
1331
     *   from 'valueList', but we'll return every element of 'valueList'
1332
     *   once before repeating any element.
1333
     *   
1334
     *   If we've returned every value on the current round, we'll
1335
     *   automatically shuffle the values and start a new round.  
1336
     */
1337
    getNextValue()
1338
    {
1339
        local i;
1340
        local ret;
1341
        local justReshuffled = nil;
1342
1343
        /* if we haven't initialized our vector, do so now */
1344
        if (valuesVec == nil)
1345
        {
1346
            /* create the vector */
1347
            valuesVec = new Vector(valueList.length(), valueList);
1348
1349
            /* all values are initially available */
1350
            valuesAvail = valuesVec.length();
1351
        }
1352
1353
        /* if we've exhausted our values on this round, start over */
1354
        if (valuesAvail == 0)
1355
        {
1356
            /* shuffle the elements */
1357
            reshuffle();
1358
1359
            /* note that we just did a shuffle */
1360
            justReshuffled = true;
1361
        }
1362
1363
        /* pick a random element from the 'available' partition */
1364
        i = rand(valuesAvail) + 1;
1365
1366
        /*
1367
         *   If we just reshuffled, and we're configured to suppress a 
1368
         *   repeat immediately after a reshuffle, and we chose the first 
1369
         *   element of the vector, and we have at least three elements, 
1370
         *   choose a different element.  The first element in the vector is 
1371
         *   always the last element we return from each run-through, since 
1372
         *   the 'available' partition is at the start of the list and thus 
1373
         *   shrinks down until it contains only the first element. 
1374
         *
1375
         *   If we have one element, there's obviously no point in trying to 
1376
         *   suppress repeats.  If we have two elements, we *still* don't 
1377
         *   want to suppress repeats, because in this case we'd generate a 
1378
         *   predicatable A-B-A-B pattern (because we could never have two 
1379
         *   A's or two B's in a row).
1380
         */
1381
        if (justReshuffled && suppressRepeats && valuesAvail > 2)
1382
        {
1383
            /* 
1384
             *   we don't want repeats, so choose anything besides the
1385
             *   first element; keep choosing until we get another element 
1386
             */
1387
            while (i == 1)
1388
                i = rand(valuesAvail) + 1;
1389
        }
1390
1391
        /* remember the element we're returning */
1392
        ret = valuesVec[i];
1393
1394
        /*
1395
         *   Move the value at the top of the 'available' partition down
1396
         *   into the hole we're creating at 'i', since we're about to
1397
         *   reduce the size of the 'available' partition to reflect the
1398
         *   use of one more value; that would leave the element at the top
1399
         *   of the partition homeless, so we need somewhere to put it.
1400
         *   Luckily, we also need to delete element 'i', since we're using
1401
         *   this element.  Solve both problems at once by moving element
1402
         *   we're rendering homeless into the hole we're creating.  
1403
         */
1404
        valuesVec[i] = valuesVec[valuesAvail];
1405
1406
        /* move the value we're returning into the top slot */
1407
        valuesVec[valuesAvail] = ret;
1408
1409
        /* reduce the 'available' partition by one */
1410
        --valuesAvail;
1411
1412
        /* return the result */
1413
        return ret;
1414
    }
1415
1416
    /*
1417
     *   Shuffle the values.  This puts all of the values back into the
1418
     *   deck (as it were) for a new round.  It's never required to call
1419
     *   this, because getNextValue() automatically shuffles the deck and
1420
     *   starts over each time it runs through the entire deck.  This is
1421
     *   provided in case the caller has a reason to want to put all the
1422
     *   values back into play immediately, before every value has been
1423
     *   dealt on the current round.  
1424
     */
1425
    reshuffle()
1426
    {
1427
        /* 
1428
         *   Simply reset the counter of available values.  Go with the
1429
         *   original source list's length, in case we haven't initialized
1430
         *   our internal vector yet. 
1431
         */
1432
        valuesAvail = valueList.length();
1433
    }
1434
1435
    /*
1436
     *   Internal vector of available/used values.  Elements from 1 to
1437
     *   'valuesAvail', inclusive, are still available for use on this
1438
     *   round.  Elements above 'valuesAvail' have already been used.  
1439
     */
1440
    valuesVec = nil
1441
    
1442
    /* number of values still available on this round */ 
1443
    valuesAvail = 0
1444
;
1445
1446
/*
1447
 *   A Shuffled Integer List is a special kind of Shuffled List that
1448
 *   returns integers in a given range.  Like an ordinary Shuffled List,
1449
 *   we'll return integers in the given range in random order, but we'll
1450
 *   only return each integer once during a given round; when we exhaust
1451
 *   the supply, we'll reshuffle the set of integers and start over.  
1452
 */
1453
class ShuffledIntegerList: ShuffledList
1454
    /* 
1455
     *   The minimum and maximum values for our range.  Instances should
1456
     *   define these to the range desired. 
1457
     */
1458
    rangeMin = 1
1459
    rangeMax = 10
1460
1461
    /* initialize the value list on demand */
1462
    valueList = nil
1463
1464
    /* construct with the given range */
1465
    construct(rmin, rmax)
1466
    {
1467
        rangeMin = rmin;
1468
        rangeMax = rmax;
1469
    }
1470
1471
    /* get the next value */
1472
    getNextValue()
1473
    {
1474
        /* if we haven't set up our value list yet, do so now */
1475
        if (valueList == nil)
1476
        {
1477
            local cnt;
1478
            local i;
1479
            
1480
            /* 
1481
             *   Set up a vector with the required number of elements.  We
1482
             *   have to add one to the difference between our max and min
1483
             *   values, because we want the set to be inclusive of the
1484
             *   endpoints. 
1485
             */
1486
            cnt = rangeMax - rangeMin + 1;
1487
            valueList = new Vector(cnt);
1488
1489
            /* put a nil value in each slot */
1490
            valueList.fillValue(nil, 1, cnt);
1491
1492
            /* now populate the slots with the integers from our range */
1493
            i = rangeMin;
1494
            valueList.applyAll({x: i++});
1495
        }
1496
1497
        /* use the inherited handling to select from our value list */
1498
        return inherited();
1499
    }
1500
;
1501
1502
1503
/* ------------------------------------------------------------------------ */
1504
/*
1505
 *   Library global variables 
1506
 */
1507
libGlobal: object
1508
    /*
1509
     *   The current library messages object.  This is the source object
1510
     *   for messages that don't logically relate to the actor carrying out
1511
     *   the comamand.  It's mostly used for meta-command replies, and for
1512
     *   text fragments that are used to construct descriptions.
1513
     *   
1514
     *   This message object isn't generally used for parser messages or
1515
     *   action replies - most of those come from the objects given by the
1516
     *   current actor's getParserMessageObj() or getActionMessageObj(),
1517
     *   respectively.
1518
     *   
1519
     *   By default, this is set to libMessages.  The library never changes
1520
     *   this itself, but a game can change this if it wants to switch to a
1521
     *   new set of messages during a game.  (If you don't need to change
1522
     *   messages during a game, but simply want to customize some of the
1523
     *   default messages, you don't need to set this variable - you can
1524
     *   simply use 'modify libMessages' instead.  This variable is
1525
     *   designed for cases where you want to *dynamically* change the
1526
     *   standard messages during the game.)  
1527
     */
1528
    libMessageObj = libMessages
1529
    
1530
    /* 
1531
     *   Sense cache - we keep SenseInfo lists here, keyed by [pov,sense];
1532
     *   we normally discard the cached information at the start of each
1533
     *   turn, and disable caching entirely at the start of the "action"
1534
     *   phase of each turn.  We leave caching disabled during each turn's
1535
     *   action phase because this is the phase where simulation state
1536
     *   changes are typically made, and hence it would be difficult to
1537
     *   keep the cache coherent during this phase.
1538
     *   
1539
     *   When this is nil, it indicates that caching is disabled.  We only
1540
     *   allow caching during certain phases of execution, when game state
1541
     *   is not conventionally altered, so that we don't have to do a lot
1542
     *   of work to keep the cache up to date.  
1543
     */
1544
    senseCache = nil
1545
1546
    /*
1547
     *   Can-Touch cache - we keep CanTouchInfo entires here, keyed by
1548
     *   [from,to].  This cache is the touch-path equivalent of the sense
1549
     *   cache, and is enabled and disabled 
1550
     */
1551
    canTouchCache = nil
1552
1553
    /* 
1554
     *   Connection list cache - this is a cache of all of the objects
1555
     *   connected by containment to a given object. 
1556
     */
1557
    connectionCache = nil
1558
1559
    /* 
1560
     *   Actor visual ambient cache - this keeps track of the ambient light
1561
     *   level at the given actor. 
1562
     */
1563
    actorVisualAmbientCache = nil
1564
1565
    /* enable the cache, clearing any old cached information */
1566
    enableSenseCache()
1567
    {
1568
        /* create a new, empty lookup table for the sense cache */
1569
        senseCache = new LookupTable(32, 64);
1570
1571
        /* create the can-touch cache */
1572
        canTouchCache = new LookupTable(32, 64);
1573
1574
        /* create the actor visual ambient cache */
1575
        actorVisualAmbientCache = new LookupTable(32, 64);
1576
1577
        /* create a connection list cache */
1578
        connectionCache = new LookupTable(32, 64);
1579
    }
1580
1581
    /* disable the cache */
1582
    disableSenseCache()
1583
    {
1584
        /* forget the cache tables */
1585
        senseCache = nil;
1586
        canTouchCache = nil;
1587
        actorVisualAmbientCache = nil;
1588
        connectionCache = nil;
1589
    }
1590
1591
    /* 
1592
     *   Invalidate the sense cache.  This can be called if something
1593
     *   happens during noun resolution or verification that causes any
1594
     *   cached sense information to become out of date.  For example, if
1595
     *   you have to create a new game-world object during noun-phrase
1596
     *   resolution, this should be called to ensure that the new object's
1597
     *   visibility is properly calculated and incorporated into the cached
1598
     *   information.  
1599
     */
1600
    invalSenseCache()
1601
    {
1602
        /* remember whether or not caching is currently enabled */
1603
        local wasEnabled = (senseCache != nil);
1604
1605
        /* clear the cache by disabling it */
1606
        disableSenseCache();
1607
1608
        /* if the cache was previously enabled, re-enable it */
1609
        if (wasEnabled)
1610
            enableSenseCache();
1611
    }
1612
1613
    /*
1614
     *   List of all of the senses.  The library pre-initializer will load
1615
     *   this list with a reference to each instance of class Sense. 
1616
     */
1617
    allSenses = []
1618
1619
    /*
1620
     *   The current player character 
1621
     */
1622
    playerChar = nil
1623
1624
    /* 
1625
     *   The current perspective actor.  This is the actor who's performing
1626
     *   the action (LOOK AROUND, EXAMINE, SMELL, etc) that's generating
1627
     *   the current description. 
1628
     */
1629
    pointOfViewActor = nil
1630
1631
    /*
1632
     *   The current perspective object.  This is *usually* the actor
1633
     *   performing the current command, but can be a different object when
1634
     *   the actor is viewing the location being described via an
1635
     *   intermediary, such as through a closed-circuit TV camera.  
1636
     */
1637
    pointOfView = nil
1638
1639
    /*
1640
     *   The stack of point of view objects.  The last element of the
1641
     *   vector is the most recent point of view after the current point
1642
     *   of view.  
1643
     */
1644
    povStack = static new Vector(32)
1645
1646
    /* 
1647
     *   The global score object.  We use a global for this, rather than
1648
     *   referencing libScore directly, to allow the score module to be
1649
     *   left out entirely if the game doesn't make use of scoring.  The
1650
     *   score module should set this during pre-initialization.  
1651
     */
1652
    scoreObj = nil
1653
1654
    /* 
1655
     *   The global Footnote class object.  We use a global for this,
1656
     *   rather than referencing Footnote directly, to allow the footnote
1657
     *   module to be left out entirely if the game doesn't make use of
1658
     *   footnotes.  The footnote class should set this during
1659
     *   pre-initialization.  
1660
     */
1661
    footnoteClass = nil
1662
1663
    /* the total number of turns so far */
1664
    totalTurns = 0
1665
1666
    /* 
1667
     *   flag: the parser is in 'debug' mode, in which it displays the
1668
     *   parse tree for each command entered 
1669
     */
1670
    parserDebugMode = nil
1671
1672
    /*
1673
     *   Most recent command, for 'undo' purposes.  This is the last
1674
     *   command the player character performed, or the last initial
1675
     *   command a player directed to an NPC.
1676
     *   
1677
     *   Note that if the player directed a series of commands to an NPC
1678
     *   with a single command line, only the first command on such a
1679
     *   command line is retained here, because it is only the first such
1680
     *   command that counts as a player's turn in terms of the game
1681
     *   clock.  Subsequent commands are executed by the NPC's on the
1682
     *   NPC's own time, and do not count against the PC's game clock
1683
     *   time.  The first command counts against the PC's clock because of
1684
     *   the time it takes the PC to give the command to the NPC.  
1685
     */
1686
    lastCommandForUndo = ''
1687
1688
    /* 
1689
     *   Most recent target actor phrase; this goes with
1690
     *   lastCommandForUndo.  This is nil if the last command did not
1691
     *   specify an actor (i.e., was implicitly for the player character),
1692
     *   otherwise is the string the player typed specifying a target
1693
     *   actor.  
1694
     */
1695
    lastActorForUndo = ''
1696
1697
    /*
1698
     *   Current command information.  We keep track of the current
1699
     *   command's actor and action here, as well as the verification
1700
     *   result list and the command report list.  
1701
     */
1702
    curActor = nil
1703
    curIssuingActor = nil
1704
    curAction = nil
1705
    curVerifyResults = nil
1706
1707
    /* the exitLister object, if included in the build */
1708
    exitListerObj = nil
1709
1710
    /* the hint manager, if included in the build */
1711
    hintManagerObj = nil
1712
;
1713
1714
/* ------------------------------------------------------------------------ */
1715
/*
1716
 *   FinishType objects are used in finishGameMsg() to indicate what kind
1717
 *   of game-over message to display.  We provide a couple of standard
1718
 *   objects for the most common cases. 
1719
 */
1720
class FinishType: object
1721
    /* the finishing message, as a string or library message property */
1722
    finishMsg = nil
1723
;
1724
1725
/* 'death' - the game has ended due to the player character's demise */
1726
ftDeath: FinishType finishMsg = &finishDeathMsg;
1727
1728
/* 'victory' - the player has won the game */
1729
ftVictory: FinishType finishMsg = &finishVictoryMsg;
1730
1731
/* 'failure' - the game has ended in failure (but not necessarily death) */
1732
ftFailure: FinishType finishMsg = &finishFailureMsg;
1733
1734
/* 'game over' - the game has simply ended */
1735
ftGameOver: FinishType finishMsg = &finishGameOverMsg;
1736
1737
/*
1738
 *   Finish the game, showing a message explaining why the game has ended.
1739
 *   This can be called when an event occurs that ends the game, such as
1740
 *   the player character's death, winning, or any other endpoint in the
1741
 *   story.
1742
 *   
1743
 *   We'll show a message defined by 'msg', using a standard format.  The
1744
 *   format depends on the language, but in English, it's usually the
1745
 *   message surrounded by asterisks: "*** You have won! ***".  'msg' can
1746
 *   be:
1747
 *   
1748
 *.    - nil, in which case we display nothing
1749
 *.    - a string, which we'll display as the message
1750
 *.    - a FinishType object, from which we'll get the message
1751
 *   
1752
 *   After showing the message (if any), we'll prompt the user with
1753
 *   options for how to proceed.  We'll always show the QUIT, RESTART, and
1754
 *   RESTORE options; other options can be offered by listing one or more
1755
 *   FinishOption objects in the 'extra' parameter, which is given as a
1756
 *   list of FinishOption objects.  The library defines a few non-default
1757
 *   finish options, such as finishOptionUndo and finishOptionCredits; in
1758
 *   addition, the game can subclass FinishOption to create its own custom
1759
 *   options, as desired.  
1760
 */
1761
finishGameMsg(msg, extra)
1762
{
1763
    local lst;
1764
1765
    /*
1766
     *   Adjust the turn counter to take into account the action currently
1767
     *   in progress, if any, and to reflect any turns that the player
1768
     *   character has already completed and which aren't yet reflected in
1769
     *   the turn counter.  If we're processing a daemon, the PC's next
1770
     *   schedulable run time will already reflect the last turn the PC
1771
     *   completed, but the global turn counter won't be there yet, since
1772
     *   we're still scheduling daemons that were ready to run on the same
1773
     *   turn as the player's last action.  
1774
     */
1775
    libGlobal.totalTurns = gPlayerChar.nextRunTime + gAction.actionTime;
1776
1777
    /*
1778
     *   Explicitly run any final score notification now.  This will ensure
1779
     *   that any points awarded in the course of the final command that
1780
     *   brought us to this point will generate the usual notification, and
1781
     *   that the notification will appear at a reasonable place, just
1782
     *   before the termination message. 
1783
     */
1784
    if (libGlobal.scoreObj != nil)
1785
        libGlobal.scoreObj.runScoreNotifier();
1786
1787
    /* translate the message, if specified */
1788
    if (dataType(msg) == TypeObject)
1789
    {
1790
        /* it's a FinishType object - get its message property or string */
1791
        msg = msg.finishMsg;
1792
1793
        /* if it's a library message property, look it up */
1794
        if (dataType(msg) == TypeProp)
1795
            msg = gLibMessages.(msg);
1796
    }
1797
1798
    /* if we have a message, display it */
1799
    if (msg != nil)
1800
        gLibMessages.showFinishMsg(msg);
1801
1802
    /* if the extra options include a scoring option, show the score */
1803
    if (extra != nil && extra.indexWhich({x: x.showScoreInFinish}) != nil)
1804
    {
1805
        "<.p>";
1806
        libGlobal.scoreObj.showScore();
1807
        "<.p>";
1808
    }
1809
1810
    /*
1811
     *   Since we need to interact directly with the player, any sense
1812
     *   context currently in effect is now irrelevant.  Reset the sense
1813
     *   context by setting the 'source' object to nil to indicate that we
1814
     *   don't need any sense blocking at all.  We can just set the context
1815
     *   directly, since this routine will never return into the
1816
     *   surrounding command processing - we always either terminate the
1817
     *   program or proceed to a different game context (via undo, restore,
1818
     *   restart, etc).  By the same token, the actor we're talking to now
1819
     *   is the player character.  
1820
     */
1821
    senseContext.setSenseContext(nil, sight);
1822
    gActor = gPlayerChar;
1823
1824
    /* start with the standard options */
1825
    lst = [finishOptionRestore, finishOptionRestart];
1826
1827
    /* add any additional options in the 'extra' parameter */
1828
    if (extra != nil)
1829
        lst += extra;
1830
1831
    /* always add 'quit' as the last option */
1832
    lst += finishOptionQuit;
1833
1834
    /* process the options */
1835
    processOptions(lst);
1836
}
1837
1838
/* finish the game, offering the given extra options but no message */
1839
finishGame(extra)
1840
{
1841
    finishGameMsg(nil, extra);
1842
}
1843
1844
/*
1845
 *   Show failed startup restore options.  If a restore operation fails at
1846
 *   startup, we won't just proceed with the game, but ask the user what
1847
 *   they want to do; we'll offer the options of restoring another game,
1848
 *   quitting, or starting the game from the beginning.  
1849
 */
1850
failedRestoreOptions()
1851
{
1852
    /* process our set of options */
1853
    processOptions([restoreOptionRestoreAnother, restoreOptionStartOver,
1854
                    finishOptionQuit]);
1855
}
1856
1857
/*
1858
 *   Process a list of finishing options.  We'll loop, showing prompts and
1859
 *   reading responses, until we get a response that terminates the loop.  
1860
 */
1861
processOptions(lst)
1862
{
1863
    /* keep going until we get a valid response */
1864
promptLoop:
1865
    for (;;)
1866
    {
1867
        local resp;
1868
        
1869
        /* show the options */
1870
        finishOptionsLister.showListAll(lst, 0, 0);
1871
1872
        /* switch to before-command mode for reading the interactive input */
1873
        "<.commandbefore>";
1874
1875
        /* 
1876
         *   update the status line, in case the score or turn counter has
1877
         *   changed (this is especially likely when we first enter this
1878
         *   loop, since we might have just finished the game with our
1879
         *   previous action, and that action might well have awarded us
1880
         *   some points) 
1881
         */
1882
        statusLine.showStatusLine();
1883
1884
        /* read a response */
1885
        resp = inputManager.getInputLine(nil, nil);
1886
1887
        /* switch to command-after mode */
1888
        "<.commandafter>";
1889
1890
        /* check for a match to each of the options in our list */
1891
        foreach (local cur in lst)
1892
        {
1893
            /* if this one matches, process the option */
1894
            if (cur.responseMatches(resp))
1895
            {
1896
                /* it matches - carry out the option */
1897
                if (cur.doOption())
1898
                {
1899
                    /* 
1900
                     *   they returned true - they want to continue asking
1901
                     *   for more options
1902
                     */
1903
                    continue promptLoop;
1904
                }
1905
                else
1906
                {
1907
                    /* 
1908
                     *   they returned nil - they want us to stop asking
1909
                     *   for options and return to our caller 
1910
                     */
1911
                    return;
1912
                }
1913
            }
1914
        }
1915
1916
        /*
1917
         *   If we got this far, it means that we didn't get a valid
1918
         *   option.  Display our "invalid option" message, and continue
1919
         *   looping so that we show the prompt again and read a new
1920
         *   option.  
1921
         */
1922
        gLibMessages.invalidFinishOption(resp);
1923
    }
1924
}
1925
1926
/*
1927
 *   Finish Option class.  This is the base class for the abstract objects
1928
 *   representing options offered by finishGame.  
1929
 */
1930
class FinishOption: object
1931
    /* 
1932
     *   The description, as displayed in the list of options.  For the
1933
     *   default English messages, this is expected to be a verb phrase in
1934
     *   infinitive form, and should show the keyword accepted as a
1935
     *   response in all capitals: "RESTART", "see some AMUSING things to
1936
     *   do", "show CREDITS". 
1937
     */
1938
    desc = ""
1939
1940
    /* 
1941
     *   By default, the item is listed.  If you want to create an
1942
     *   invisible option that's accepted but which isn't listed in the
1943
     *   prompt, just set this to nil.  Invisible options are sometimes
1944
     *   useful when the output of one option mentions another option; for
1945
     *   example, the CREDITS message might mention a LICENSE command for
1946
     *   displaying the license, so you want to make that command available
1947
     *   without cluttering the prompt with it.  
1948
     */
1949
    isListed = true
1950
1951
    /* our response keyword */
1952
    responseKeyword = ''
1953
1954
    /* 
1955
     *   a single character we accept as an alternative to our full
1956
     *   response keyword, or nil if we don't accept a single-character
1957
     *   response 
1958
     */
1959
    responseChar = nil
1960
    
1961
    /* 
1962
     *   Match a response string to this option.  Returns true if the
1963
     *   string matches our response, nil otherwise.  By default, we'll
1964
     *   return true if the string exactly matches responseKeyword or
1965
     *   exactly matches our responseChar (if that's non-nil), but this
1966
     *   can be overridden to match other strings if desired.  By default,
1967
     *   we'll match the response without regard to case.
1968
     */
1969
    responseMatches(response)
1970
    {
1971
        /* do all of our work in lower-case */
1972
        response = response.toLower();
1973
1974
        /* 
1975
         *   check for a match the full response keyword or to the single
1976
         *   response character 
1977
         */
1978
        return (response == responseKeyword.toLower()
1979
                || (responseChar != nil
1980
                    && response == responseChar.toLower()));
1981
    }
1982
1983
    /*
1984
     *   Carry out the option.  This is called when the player enters a
1985
     *   response that matches this option.  This routine must perform the
1986
     *   action of the option, then return true to indicate that we should
1987
     *   ask for another option, or nil to indicate that the finishGame()
1988
     *   routine should simply return.  
1989
     */
1990
    doOption()
1991
    {
1992
        /* tell finishGame() to ask for another option */
1993
        return true;
1994
    }
1995
1996
    /* 
1997
     *   Flag: show the score with the end-of-game announcement.  If any
1998
     *   option in the list of finishing options has this flag set, we'll
1999
     *   show the score using the same message that the SCORE command
2000
     *   uses. 
2001
     */
2002
    showScoreInFinish = nil
2003
;
2004
2005
/*
2006
 *   QUIT option for finishGame.  The language-specific code should modify
2007
 *   this to specify the description and response keywords.  
2008
 */
2009
finishOptionQuit: FinishOption
2010
    doOption()
2011
    {
2012
        /* 
2013
         *   carry out the Quit action - this will signal a
2014
         *   QuittingException, so this call will never return 
2015
         */
2016
        QuitAction.terminateGame();
2017
    }
2018
;
2019
2020
/*
2021
 *   RESTORE option for finishGame. 
2022
 */
2023
finishOptionRestore: FinishOption
2024
    doOption()
2025
    {
2026
        /* 
2027
         *   Try restoring.  If this succeeds (i.e., it returns true), tell
2028
         *   the caller to stop looping and to proceed with the game by
2029
         *   returning nil.  If this fails, tell the caller to keep looping
2030
         *   by returning true.
2031
         */
2032
        if (RestoreAction.askAndRestore())
2033
        {
2034
            /* 
2035
             *   we succeeded, so we're now restored to some prior game
2036
             *   state - terminate any remaining processing in the command
2037
             *   that triggered the end-of-game options
2038
             */
2039
            throw new TerminateCommandException();
2040
        }
2041
        else
2042
        {
2043
            /* it failed - tell the caller to keep looping */
2044
            return true;
2045
        }
2046
    }
2047
;
2048
2049
/*
2050
 *   RESTART option for finishGame 
2051
 */
2052
finishOptionRestart: FinishOption
2053
    doOption()
2054
    {
2055
        /* 
2056
         *   carry out the restart - this will not return, since we'll
2057
         *   reset the game state and re-enter the game at the restart
2058
         *   entrypoint 
2059
         */
2060
        RestartAction.doRestartGame();
2061
    }
2062
;
2063
2064
/*
2065
 *   START FROM BEGINNING option for failed startup restore.  This is just
2066
 *   like finishOptionRestart, but shows a different option name.  
2067
 */
2068
restoreOptionStartOver: finishOptionRestart
2069
;
2070
2071
/* 
2072
 *   RESTORE ANOTHER GAME option for failed startup restore.  This is just
2073
 *   like finishOptionRestore, but shows a different option name. 
2074
 */
2075
restoreOptionRestoreAnother: finishOptionRestore
2076
;
2077
2078
/*
2079
 *   UNDO option for finishGame 
2080
 */
2081
finishOptionUndo: FinishOption
2082
    doOption()
2083
    {
2084
        /* try performing the undo */
2085
        if (UndoAction.performUndo(nil))
2086
        {
2087
            /* act as though UNDO were the last actual command, for AGAIN */
2088
            AgainAction.saveForAgain(gPlayerChar, gPlayerChar,
2089
                                     nil, UndoAction);
2090
            
2091
            /* 
2092
             *   Success - terminate the current command with no further
2093
             *   processing.
2094
             */
2095
            throw new TerminateCommandException();
2096
        }
2097
        else
2098
        {
2099
            /* 
2100
             *   failure - show a blank line and tell the caller to ask
2101
             *   for another option, since we couldn't carry out this
2102
             *   option 
2103
             */
2104
            "<.p>";
2105
            return true;
2106
        }
2107
    }
2108
;
2109
2110
/*
2111
 *   FULL SCORE option for finishGame
2112
 */
2113
finishOptionFullScore: FinishOption
2114
    doOption()
2115
    {
2116
        /* show a blank line before the score display */
2117
        "\b";
2118
2119
        /* run the Full Score action */
2120
        FullScoreAction.showFullScore();
2121
2122
        /* show a paragraph break after the score display */
2123
        "<.p>";
2124
2125
        /* 
2126
         *   this option has now had its full effect, so tell the caller
2127
         *   to go back and ask for a new option 
2128
         */
2129
        return true;
2130
    }
2131
2132
    /* 
2133
     *   by default, show the score with the end-of-game announcement when
2134
     *   this option is included 
2135
     */
2136
    showScoreInFinish = true
2137
;
2138
2139
/*
2140
 *   Option to show the score in finishGame.  This doesn't create a listed
2141
 *   option in the set of offered options, but rather is simply a flag to
2142
 *   finishGame() that the score should be announced along with the
2143
 *   end-of-game announcement message. 
2144
 */
2145
finishOptionScore: FinishOption
2146
    /* show the score in the end-of-game announcement */
2147
    showScoreInFinish = true
2148
2149
    /* this is not a listed option */
2150
    isListed = nil
2151
2152
    /* this option isn't selectable, so it has no effect */
2153
    doOption() { }
2154
;
2155
2156
/*
2157
 *   CREDITS option for finishGame 
2158
 */
2159
finishOptionCredits: FinishOption
2160
    doOption()
2161
    {
2162
        /* show a blank line before the credits */
2163
        "\b";
2164
2165
        /* run the Credits action */
2166
        CreditsAction.execSystemAction();
2167
2168
        /* show a paragraph break after the credits */
2169
        "<.p>";
2170
2171
        /* 
2172
         *   this option has now had its full effect, so tell the caller
2173
         *   to go back and ask for a new option 
2174
         */
2175
        return true;
2176
    }
2177
;
2178
2179
/*
2180
 *   AMUSING option for finishGame 
2181
 */
2182
finishOptionAmusing: FinishOption
2183
    /*
2184
     *   The game must modify this object to define a doOption method.  We
2185
     *   have no built-in way to show a list of amusing things to try, so
2186
     *   if a game wants to offer this option, it must provide a suitable
2187
     *   definition here.  (We never offer this option by default, so a
2188
     *   game need not provide a definition if the game doesn't explicitly
2189
     *   offer this option via the 'extra' argument to finishGame()).  
2190
     */
2191
;
2192
2193
/* ------------------------------------------------------------------------ */
2194
/*
2195
 *   The settings user interface.  This is a subclass of the Settings
2196
 *   Manager that adds a command-line user interface, particularly to allow
2197
 *   the user to view, save, and load the default settings.  
2198
 *   
2199
 *   Our user interface consists mainly of a pair of special commands: SAVE
2200
 *   DEFAULTS and RESTORE DEFAULTS.  The SAVE DEFAULTS command tells the
2201
 *   library to write out all of the current settings (at least, all of
2202
 *   those that participate in this framework) to a file.  RESTORE DEFAULTS
2203
 *   explicitly reads that same file and puts the stored settings into
2204
 *   effect.  Finally, we'll also read the file and activate its stored
2205
 *   settings when we start (or RESTART) the game.
2206
 *   
2207
 */
2208
settingsUI: settingsManager
2209
    /* display all of the current settings */
2210
    showAll()
2211
    {
2212
        local first = true;
2213
2214
        /* loop over all SettingsItem instances */
2215
        forEachInstance(SettingsItem, new function(item)
2216
        {
2217
            /* add a separator if this isn't the first one */
2218
            if (!first)
2219
                gLibMessages.settingsItemSeparator;
2220
            
2221
            /* show this item's description */
2222
            item.settingDesc;
2223
            
2224
            /* it's no longer the first */
2225
            first = nil;
2226
        });
2227
    }
2228
2229
    /* 
2230
     *   Save settings, and display an acknowledgment message (or an error
2231
     *   message, if necessary) for the user's edification.
2232
     */
2233
    saveSettingsMsg()
2234
    {
2235
        /* catch any errors */
2236
        try
2237
        {
2238
            /* save the settings */
2239
            saveSettings();
2240
2241
            /* if we got this far, declare success */
2242
            gLibMessages.savedDefaults();
2243
        }
2244
        catch (FileCreationException fce)
2245
        {
2246
            /* we couldn't open the file */
2247
            gLibMessages.defaultsFileWriteError;
2248
        }
2249
    }
2250
2251
    /*
2252
     *   Restore settings, and display an acknowledgment or error message,
2253
     *   as appropriate.  
2254
     */
2255
    restoreSettingsMsg()
2256
    {
2257
        /* catch any errors */
2258
        try
2259
        {
2260
            /* restore the settings */
2261
            restoreSettings();
2262
2263
            /* if we got this far, declare success */
2264
            gLibMessages.restoredDefaults();
2265
        }
2266
        catch (SettingsNotSupportedException sns)
2267
        {
2268
            /* this interpreter doesn't support the settings file */
2269
            gLibMessages.defaultsFileNotSupported;
2270
        }
2271
    }
2272
;
2273
2274
/* ------------------------------------------------------------------------ */
2275
/*
2276
 *   Utility functions 
2277
 */
2278
2279
/*
2280
 *   nilToList - convert a 'nil' value to an empty list.  This can be
2281
 *   useful for mix-in classes that will be used in different inheritance
2282
 *   contexts, since the classes might or might not inherit a base class
2283
 *   definition for list-valued methods such as preconditions.  This
2284
 *   provides a usable default for list-valued methods that return nothing
2285
 *   from superclasses. 
2286
 */
2287
nilToList(val)
2288
{
2289
    return (val != nil ? val : []);
2290
}
2291
2292
/* 
2293
 *   partitionList - partition a list into a pair of two lists, the first
2294
 *   containing items that match the predicate 'fn', the second containing
2295
 *   items that don't match 'fn'.  'fn' is a function pointer (usually an
2296
 *   anonymous function) that takes a single argument - a list element -
2297
 *   and returns true or nil.
2298
 *   
2299
 *   The return value is a list with two elements.  The first element is a
2300
 *   list giving the elements of the original list for which 'fn' returns
2301
 *   true, the second element is a list giving the elements for which 'fn'
2302
 *   returns nil.
2303
 *   
2304
 *   (Contributed by Tommy Nordgren.)  
2305
 */
2306
partitionList(lst, fn)
2307
{
2308
    local lst1 = lst.subset(fn);
2309
    local lst2 = lst.subset({x : !fn(x)});
2310
    
2311
    return [lst1, lst2];
2312
}
2313
2314
/*
2315
 *   Determine if list a is a subset of list b.  a is a subset of b if
2316
 *   every element of a is in b.  
2317
 */
2318
isListSubset(a, b)
2319
{
2320
    /* a can't be a subset if it has more elements than b */
2321
    if (a.length() > b.length())
2322
        return nil;
2323
    
2324
    /* check each element of a to see if it's also in b */
2325
    foreach (local cur in a)
2326
    {
2327
        /* if this element of a is not in b, a is not a subset of b */
2328
        if (b.indexOf(cur) == nil)
2329
            return nil;
2330
    }
2331
2332
    /* 
2333
     *   we didn't find any elements of a that are not also in b, so a is a
2334
     *   subset of b 
2335
     */
2336
    return true;
2337
}
2338