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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library: Actions.
7
 *   
8
 *   This module defines the set of built-in library actions.  
9
 */
10
11
#include "adv3.h"
12
#include "tok.h"
13
14
/* ------------------------------------------------------------------------ */
15
/*
16
 *   Special "debug" action - this simply breaks into the debugger, if the
17
 *   debugger is present. 
18
 */
19
DefineIAction(Debug)
20
    execAction()
21
    {
22
        /* if the debugger is present, break into it */
23
        if (t3DebugTrace(T3DebugCheck))
24
            t3DebugTrace(T3DebugBreak);
25
        else
26
            "Debugger not present. ";
27
    }
28
;
29
30
/* ------------------------------------------------------------------------ */
31
/*
32
 *   Special internal action to note a change to the darkness level.  This
33
 *   command is invoked internally when a change to the darkness level
34
 *   occurs.  
35
 */
36
DefineIAction(NoteDarkness)
37
    execAction()
38
    {
39
        /* 
40
         *   if we're in the dark, note that darkness has fallen;
41
         *   otherwise, show the player character's room description as
42
         *   though the player had typed "look" 
43
         */
44
        if (gActor.isLocationLit())
45
        {
46
            /* look around */
47
            gActor.lookAround(true);
48
        }
49
        else
50
        {
51
            /* it is now dark */
52
            mainReport(&newlyDarkMsg);
53
        }
54
    }
55
56
    /* this is an internal command that takes no time */
57
    actionTime = 0
58
59
    /* this isn't a real action, so it's not repeatable */
60
    isRepeatable = nil
61
62
    /* this action doesn't do anything; don't include it in undo */
63
    includeInUndo = nil
64
;
65
66
/* ------------------------------------------------------------------------ */
67
/*
68
 *   Special "again" action.  This command repeats the previous command.
69
 */
70
DefineIAction(Again)
71
    /* for obvious reasons, 'again' is not itself repeatable with 'again' */
72
    isRepeatable = nil
73
74
    /* 
75
     *   the undo command itself is not undoable (but the underlying
76
     *   command that we repeat might be) 
77
     */
78
    includeInUndo = nil
79
80
    /* information on the most recent command */
81
    lastIssuingActor = nil
82
    lastTargetActor = nil
83
    lastTargetActorPhrase = nil
84
    lastAction = nil
85
86
    /* save the most recent command so that it can be repeated if desired */
87
    saveForAgain(issuingActor, targetActor, targetActorPhrase, action)
88
    {
89
        /* save the information */
90
        lastIssuingActor = issuingActor;
91
        lastTargetActor = targetActor;
92
        lastTargetActorPhrase = targetActorPhrase;
93
        lastAction = action.createClone();
94
    }
95
96
    /* forget the last command, so that AGAIN cannot be used */
97
    clearForAgain() { lastAction = nil; }
98
99
    /* 
100
     *   Execute the 'again' command.  This action is special enough that
101
     *   we override its entire action processing sequence - this is
102
     *   necessary in case we're repeating another special command, such
103
     *   as 'again', and in any case is desirable because we don't want
104
     *   'again' to count as a command in its own right; it's essentially
105
     *   just a macro that we replace with the original command. 
106
     */
107
    doAction(issuingActor, targetActor, targetActorPhrase,
108
             countsAsIssuerTurn)
109
    {
110
        /* if there's nothing to repeat, show an error and give up */
111
        if (lastAction == nil)
112
        {
113
            gLibMessages.noCommandForAgain();
114
            return;
115
        }
116
117
        /* 
118
         *   'again' cannot be executed with a target actor - the target
119
         *   actor must be the player character 
120
         */
121
        if (!targetActor.isPlayerChar)
122
        {
123
            gLibMessages.againCannotChangeActor();
124
            return;
125
        }
126
127
        /* 
128
         *   if the issuing actor isn't the same as the target actor, make
129
         *   sure the issuer can still talk to the target 
130
         */
131
        if (lastIssuingActor != lastTargetActor
132
            && !lastIssuingActor.canTalkTo(lastTargetActor))
133
        {
134
            /* complain that we can no longer talk to the target */
135
            gLibMessages.againCannotTalkToTarget(
136
                lastIssuingActor, lastTargetActor);
137
            return;
138
        }
139
140
        /*
141
         *   If the last issuing actor is different from the last target
142
         *   actor, then the command counts as an issuer turn, because
143
         *   we're effectively repeating the entire last command, including
144
         *   the target actor specification.  That is, after a command like
145
         *   "bob, go east", saying "again" is just like saying "bob, go
146
         *   east" again, which counts as an issuer turn.  
147
         */
148
        if (lastTargetActor != lastIssuingActor)
149
            countsAsIssuerTurn = true;
150
151
        /* reset any cached information for the new command context */
152
        lastAction.resetAction();
153
        
154
        /* repeat the action */
155
        lastAction.repeatAction(lastTargetActor, lastTargetActorPhrase,
156
                                lastIssuingActor, countsAsIssuerTurn);
157
158
        /*
159
         *   If the command was directed from the issuer to a different
160
         *   target actor, and the issuer wants to wait for the full set of
161
         *   issued commands to complete before getting another turn, tell
162
         *   the issuer to begin waiting.  
163
         */
164
        if (lastTargetActor != lastIssuingActor)
165
            lastIssuingActor.waitForIssuedCommand(lastTargetActor);
166
    }
167
168
    /* 
169
     *   this command itself consumes no time on the game clock (although
170
     *   the action we perform might) 
171
     */
172
    actionTime = 0
173
;
174
175
/* ------------------------------------------------------------------------ */
176
/*
177
 *   PreSaveObject - every instance of this class is notified, via its
178
 *   execute() method, just before we save the game.  This uses the
179
 *   ModuleExecObject framework, so the sequencing lists (execBeforeMe,
180
 *   execAfterMe) can be used to control relative ordering of execution
181
 *   among instances.  
182
 */
183
class PreSaveObject: ModuleExecObject
184
    /*
185
     *   Each instance must override execute() with its specific pre-save
186
     *   code. 
187
     */
188
;
189
190
/*
191
 *   PostRestoreObject - every instance of this class is notified, via its
192
 *   execute() method, immediately after we restore the game. 
193
 */
194
class PostRestoreObject: ModuleExecObject
195
    /* 
196
     *   note: each instance must override execute() with its post-restore
197
     *   code 
198
     */
199
200
    /*
201
     *   The "restore code," which is the (normally integer) value passed
202
     *   as the second argument to restoreGame().  The restore code gives
203
     *   us some idea of what triggered the restoration.  By default, we
204
     *   define the following restore codes:
205
     *   
206
     *   1 - the system is restoring a game as part of interpreter
207
     *   startup, usually because the user explicitly specified a game to
208
     *   restore on the interpreter command line or via a GUI shell
209
     *   mechanism, such as double-clicking on a saved game file from the
210
     *   desktop.
211
     *   
212
     *   2 - the user is explicitly restoring a game via a RESTORE command.
213
     *   
214
     *   Games and library extensions can use their own additional restore
215
     *   codes in their calls to restoreGame().  
216
     */
217
    restoreCode = nil
218
;
219
220
/*
221
 *   PreRestartObject - every instance of this class is notified, via its
222
 *   execute() method, just before we restart the game (with a RESTART
223
 *   command, for example). 
224
 */
225
class PreRestartObject: ModuleExecObject
226
    /* 
227
     *   Each instance must override execute() with its specific
228
     *   pre-restart code.  
229
     */
230
;
231
232
/*
233
 *   PostUndoObject - every instance of this class is notified, via its
234
 *   execute() method, immediately after we perform an 'undo' command. 
235
 */
236
class PostUndoObject: ModuleExecObject
237
    /* 
238
     *   Each instance must override execute() with its specific post-undo
239
     *   code.  
240
     */
241
;
242
243
/* ------------------------------------------------------------------------ */
244
/*
245
 *   Special "save" action.  This command saves the current game state to
246
 *   an external file for later restoration. 
247
 */
248
DefineSystemAction(Save)
249
    execSystemAction()
250
    {
251
        local result;
252
        local origElapsedTime;
253
254
        /* note the current elapsed game time */
255
        origElapsedTime = realTimeManager.getElapsedTime();
256
257
        /* ask for a file */
258
        result = getInputFile(gLibMessages.getSavePrompt(), InFileSave,
259
                              FileTypeT3Save, 0);
260
261
        /* check the inputFile response */
262
        switch(result[1])
263
        {
264
        case InFileSuccess:
265
            /* perform the save on the given file */
266
            performSave(result[2]);
267
268
            /* done */
269
            break;
270
271
        case InFileFailure:
272
            /* advise of the failure of the prompt */
273
            gLibMessages.filePromptFailed();
274
            break;
275
276
        case InFileCancel:
277
            /* acknowledge the cancellation */
278
            gLibMessages.saveCanceled();
279
            break;
280
        }
281
282
        /* 
283
         *   restore the original elapsed game time, so that the time spent
284
         *   in the file selector dialog doesn't count against the game
285
         *   time 
286
         */
287
        realTimeManager.setElapsedTime(origElapsedTime);
288
    }
289
290
    /* perform a save */
291
    performSave(fname)
292
    {
293
        /* before saving the game, notify all PreSaveObject instances */
294
        PreSaveObject.classExec();
295
        
296
        /* 
297
         *   Save the game to the given file.  If an error occurs, the
298
         *   save routine will throw a runtime error.  
299
         */
300
        try
301
        {
302
            /* try saving the game */
303
            saveGame(fname);
304
        }
305
        catch (RuntimeError err)
306
        {
307
            /* the save failed - mention the problem */
308
            gLibMessages.saveFailed(err);
309
            
310
            /* done */
311
            return;
312
        }
313
        
314
        /* note the successful save */
315
        gLibMessages.saveOkay();
316
    }
317
318
    /* 
319
     *   Saving has no effect on game state, so it's irrelevant whether or
320
     *   not it's undoable; but it might be confusing to say we undid a
321
     *   "save" command, because the player might think we deleted the
322
     *   saved file.  To avoid such confusion, do not include "save"
323
     *   commands in the undo log.  
324
     */
325
    includeInUndo = nil
326
327
    /* 
328
     *   Don't allow this to be repeated with AGAIN.  There's no point in
329
     *   repeating a SAVE immediately, as nothing will have changed in the
330
     *   game state to warrant saving again.  
331
     */
332
    isRepeatable = nil
333
;
334
335
/*
336
 *   Subclass of Save action that takes a literal string as part of the
337
 *   command.  The filename must be a literal enclosed in quotes, and the
338
 *   string (with the quotes) must be stored in our fname_ property by
339
 *   assignment of a quotedStringPhrase production in the grammar rule.  
340
 */
341
DefineAction(SaveString, SaveAction)
342
    execSystemAction()
343
    {
344
        /* 
345
         *   Perform the save, using the filename given in our fname_
346
         *   parameter, trimmed of quotes.  
347
         */
348
        performSave(fname_.getStringText());
349
    }
350
;
351
352
353
/* ------------------------------------------------------------------------ */
354
/*
355
 *   Special "restore" action.  This action restores game state previously
356
 *   saved with the "save" action.
357
 */
358
DefineSystemAction(Restore)
359
    execSystemAction()
360
    {
361
        /* ask for a file and restore it */
362
        askAndRestore();
363
364
        /* 
365
         *   regardless of what happened, abandon any additional commands
366
         *   on the same command line 
367
         */
368
        throw new TerminateCommandException();
369
    }
370
371
    /*
372
     *   Ask for a file and try to restore it.  Returns true on success,
373
     *   nil on failure.  (Failure could indicate that the user chose to
374
     *   cancel out of the file selector, that we couldn't find the file to
375
     *   restore, or that the file isn't a valid saved state file.  In any
376
     *   case, we show an appropriate message on failure.)  
377
     */
378
    askAndRestore()
379
    {
380
        local succ;        
381
        local result;
382
        local origElapsedTime;
383
384
        /* presume failure */
385
        succ = nil;
386
387
        /* note the current elapsed game time */
388
        origElapsedTime = realTimeManager.getElapsedTime();
389
390
        /* ask for a file */
391
        result = getInputFile(gLibMessages.getRestorePrompt(), InFileOpen,
392
                              FileTypeT3Save, 0);
393
394
        /* 
395
         *   restore the real-time clock, so that the time spent in the
396
         *   file selector dialog doesn't count against the game time 
397
         */
398
        realTimeManager.setElapsedTime(origElapsedTime);
399
400
        /* check the inputFile response */
401
        switch(result[1])
402
        {
403
        case InFileSuccess:
404
            /* 
405
             *   try restoring the file; use code 2 to indicate that the
406
             *   restoration was performed by an explicit RESTORE command 
407
             */
408
            if (performRestore(result[2], 2))
409
            {
410
                /* note that we succeeded */
411
                succ = true;
412
            }
413
            else
414
            {
415
                /* 
416
                 *   failed - in case the failed restore took some time,
417
                 *   restore the real-time clock, so that the file-reading
418
                 *   time doesn't count against the game time 
419
                 */
420
                realTimeManager.setElapsedTime(origElapsedTime);
421
            }
422
423
            /* done */
424
            break;
425
426
        case InFileFailure:
427
            /* advise of the failure of the prompt */
428
            gLibMessages.filePromptFailed();
429
            break;
430
431
        case InFileCancel:
432
            /* acknowledge the cancellation */
433
            gLibMessages.restoreCanceled();
434
            break;
435
        }
436
437
        /* 
438
         *   If we were successful, clear out the AGAIN memory.  This
439
         *   avoids any confusion about whether we're repeating the RESTORE
440
         *   command itself, the command just before RESTORE from the
441
         *   current session, or the last command before SAVE from the
442
         *   restored game. 
443
         */
444
        if (succ)
445
            AgainAction.clearForAgain();
446
447
        /* return the success/failure indication */
448
        return succ;
449
    }
450
451
    /*
452
     *   Restore a game on startup.  This can be called from mainRestore()
453
     *   to restore a saved game directly as part of loading the game.
454
     *   (Most interpreters provide a way of starting the interpreter
455
     *   directly with a saved game to be restored, skipping the
456
     *   intermediate step of running the game and using a RESTORE
457
     *   command.)
458
     *   
459
     *   Returns true on success, nil on failure.  On failure, the caller
460
     *   should simply exit the program.  On success, the caller should
461
     *   start the game running, usually using runGame(), after showing any
462
     *   desired introductory messages.  
463
     */
464
    startupRestore(fname)
465
    {
466
        /* 
467
         *   try restoring the game, using code 1 to indicate that this is
468
         *   a direct startup restore 
469
         */
470
        if (performRestore(fname, 1))
471
        {
472
            /* success - tell the caller to proceed with the restored game */
473
            return true;
474
        }
475
        else
476
        {
477
            /* 
478
             *   Failure.  We've described the problem, so ask the user
479
             *   what they want to do about it. 
480
             */
481
            try
482
            {
483
                /* show options and read the response */
484
                failedRestoreOptions();
485
486
                /* if we get here, proceed with the game */
487
                return true;
488
            }
489
            catch (QuittingException qe)
490
            {
491
                /* quitting - tell the caller to terminate */
492
                return nil;
493
            }
494
        }
495
    }
496
    
497
498
    /*
499
     *   Restore a file.  'code' is the restoreCode value for the
500
     *   PostRestoreObject notifications.  Returns true on success, nil on
501
     *   failure.  
502
     */
503
    performRestore(fname, code)
504
    {
505
        try
506
        {
507
            /* restore the file */
508
            restoreGame(fname);
509
        }
510
        catch (RuntimeError err)
511
        {
512
            /* failed - check the error to see what went wrong */
513
            switch(err.errno_)
514
            {
515
            case 1201:
516
                /* not a saved state file */
517
                gLibMessages.restoreInvalidFile();
518
                break;
519
                
520
            case 1202:
521
                /* saved by different game or different version */
522
                gLibMessages.restoreInvalidMatch();
523
                break;
524
                
525
            case 1207:
526
                /* corrupted saved state file */
527
                gLibMessages.restoreCorruptedFile();
528
                break;
529
                
530
            default:
531
                /* some other failure */
532
                gLibMessages.restoreFailed(err);
533
                break;
534
            }
535
536
            /* indicate failure */
537
            return nil;
538
        }
539
540
        /* note that we've successfully restored the game */
541
        gLibMessages.restoreOkay();
542
        
543
        /* set the appropriate restore-action code */
544
        PostRestoreObject.restoreCode = code;
545
546
        /* notify all PostRestoreObject instances */
547
        PostRestoreObject.classExec();
548
549
        /* 
550
         *   look around, to refresh the player's memory of the state the
551
         *   game was in when saved 
552
         */
553
        "\b";
554
        libGlobal.playerChar.lookAround(true);
555
556
        /* indicate success */
557
        return true;
558
    }
559
    
560
    /* 
561
     *   There's no point in including this in undo.  If the command
562
     *   succeeds, it's not undoable itself, and there won't be any undo
563
     *   information in the newly restored state.  If the command fails, it
564
     *   won't make any changes to the game state, so there won't be
565
     *   anything to undo.  
566
     */
567
    includeInUndo = nil
568
;
569
570
/*
571
 *   Subclass of Restore action that takes a literal string as part of the
572
 *   command.  The filename must be a literal enclosed in quotes, and the
573
 *   string (with the quotes) must be stored in our fname_ property by
574
 *   assignment of a quotedStringPhrase production in the grammar rule.  
575
 */
576
DefineAction(RestoreString, RestoreAction)
577
    execSystemAction()
578
    {
579
        /* 
580
         *   Perform the restore, using the filename given in our fname_
581
         *   parameter, trimmed of quotes.  Use code 2, the same as any
582
         *   other explicit RESTORE command.  
583
         */
584
        performRestore(fname_.getStringText(), 2);
585
586
        /* abandon any additional commands on the same command line */
587
        throw new TerminateCommandException();
588
    }
589
;
590
591
/* ------------------------------------------------------------------------ */
592
/*
593
 *   Restart the game from the beginning.
594
 */
595
DefineSystemAction(Restart)
596
    execSystemAction()
597
    {
598
        /* confirm that they really want to restart */
599
        gLibMessages.confirmRestart();
600
        if (yesOrNo())
601
        {
602
            /* 
603
             *   The confirmation input will have put us into
604
             *   start-of-command mode for sequencing purposes; force the
605
             *   sequencer back to mid-command mode, so we can show
606
             *   inter-command separation before the restart. 
607
             */
608
609
            /* restart the game */
610
            doRestartGame();
611
        }
612
        else
613
        {
614
            /* confirm that we're not really restarting */
615
            gLibMessages.notRestarting();
616
        }
617
    }
618
619
    /* carry out the restart action */
620
    doRestartGame()
621
    {
622
        /* 
623
         *   Show a command separator, to provide separation from any
624
         *   introductory text that we'll show on restarting.  Note that
625
         *   we probably just asked for confirmation, which means that the
626
         *   command sequencer will be in start-of-command mode; force it
627
         *   back to mid-command mode so we show inter-command separation.
628
         */
629
        commandSequencer.setCommandMode();
630
        "<.commandsep>";
631
632
        /* before restarting, notify anyone interested of our intentions */
633
        PreRestartObject.classExec();
634
635
        /* 
636
         *   Throw a 'restart' signal; the main entrypoint loop will catch
637
         *   this and actually perform the restart.
638
         *   
639
         *   Note that we *could* do the VM reset (via restartGame()) here,
640
         *   but there's an advantage to doing it in the main loop: we
641
         *   won't be in the stack context of whatever command we're
642
         *   performing.  If we did the restart here, it's possible that
643
         *   some useless objects would survive the VM reset just because
644
         *   they're referenced from within a caller's stack frame.  Those
645
         *   objects would immediately go out of scope when we get back to
646
         *   the main loop, but they might survive long enough to create
647
         *   apparent inconsistencies.  In particular, if we did a
648
         *   firstObj/nextObj loop, we could discover those objects and
649
         *   re-establish more lasting references to them, which we
650
         *   certainly don't want to do.  By deferring the VM reset until
651
         *   we get back to the main loop, we'll ensure that objects won't
652
         *   survive the reset just because they're on the stack
653
         *   momentarily here.  
654
         */
655
        throw new RestartSignal();
656
    }
657
658
    /* there's no point in including this in undo */
659
    includeInUndo = nil
660
;
661
662
/* ------------------------------------------------------------------------ */
663
/*
664
 *   Undo one turn. 
665
 */
666
DefineSystemAction(Undo)
667
    /*
668
     *   "Undo" is so special that we must override the entire action
669
     *   processing sequence.  We do this because undoing will restore the
670
     *   game state as of the previous savepoint, which would leave all
671
     *   sorts of things unsynchronized in the normal action sequence.  To
672
     *   avoid problems, we simply leave out any other action processing
673
     *   and perform the 'undo' directly.  
674
     */
675
    doAction(issuingActor, targetActor, targetActorPhrase,
676
             countsAsIssuerTurn)
677
    {
678
        /* 
679
         *   the player obviously knows about UNDO, so there's no need for
680
         *   a tip about it 
681
         */
682
        undoTip.makeShown();
683
684
        /* 
685
         *   don't allow this unless the player character is performing
686
         *   the command directly
687
         */
688
        if (!targetActor.isPlayerChar)
689
        {
690
            /* 
691
             *   tell them this command cannot be directed to another
692
             *   actor, and give up 
693
             */
694
            gLibMessages.systemActionToNPC();
695
            return;
696
        }
697
698
        /* perform the undo */
699
        performUndo(true);
700
    }
701
702
    /* 
703
     *   Perform undo.  Returns true if we were successful, nil if not.
704
     *   
705
     *   'asCommand' indicates whether or not the undo is being performed
706
     *   as an explicit command: if so, we'll save the UNDO command for use
707
     *   in AGAIN.  
708
     */
709
    performUndo(asCommand)
710
    {
711
        /* try undoing to the previous savepoint */
712
        if (undo())
713
        {
714
            local oldActor;
715
            local oldIssuer;
716
            local oldAction;
717
718
            /* notify all PostUndoObject instances */
719
            PostUndoObject.classExec();
720
721
            /* set up the globals for the command */
722
            oldActor = gActor;
723
            oldIssuer = gIssuingActor;
724
            oldAction = gAction;
725
726
            /* set the new globals */
727
            gActor = gPlayerChar;
728
            gIssuingActor = gPlayerChar;
729
            gAction = self;
730
731
            /* make sure we reset globals on the way out */
732
            try
733
            {
734
                /* success - mention what we did */
735
                gLibMessages.undoOkay(libGlobal.lastActorForUndo,
736
                                      libGlobal.lastCommandForUndo);
737
                
738
                /* look around, to refresh the player's memory */
739
                libGlobal.playerChar.lookAround(true);
740
            }
741
            finally
742
            {
743
                /* restore the parser globals to how we found them */
744
                gActor = oldActor;
745
                gIssuingActor = oldIssuer;
746
                gAction = oldAction;
747
            }
748
                
749
            /* 
750
             *   if this was an explicit 'undo' command, save the command
751
             *   to allow repeating it with 'again' 
752
             */
753
            if (asCommand)
754
                AgainAction.saveForAgain(gPlayerChar, gPlayerChar, nil, self);
755
756
            /* indicate success */
757
            return true;
758
        }
759
        else
760
        {
761
            /* no more undo information available */
762
            gLibMessages.undoFailed();
763
764
            /* indicate failure */
765
            return nil;
766
        }
767
    }
768
769
    /* 
770
     *   "undo" is not undoable - if we undo again after an undo, we undo
771
     *   the next most recent command
772
     */
773
    includeInUndo = nil
774
;
775
776
/* ------------------------------------------------------------------------ */
777
/*
778
 *   Save the defaults 
779
 */
780
DefineSystemAction(SaveDefaults)
781
    execSystemAction()
782
    {
783
        /* tell SettingsItem to save all settings */
784
        settingsUI.saveSettingsMsg();
785
    }
786
787
    /* there's no point in including this in undo */
788
    includeInUndo = nil
789
;
790
791
/*
792
 *   Restore defaults 
793
 */
794
DefineSystemAction(RestoreDefaults)
795
    execSystemAction()
796
    {
797
        /* 
798
         *   Tell SettingsItem to restore all settings.  This is an
799
         *   explicit request, so we want SettingsItem to describe what
800
         *   happened. 
801
         */
802
        settingsUI.restoreSettingsMsg();
803
    }
804
805
    /* there's no point in including this in undo */
806
    includeInUndo = nil
807
;
808
809
810
/* ------------------------------------------------------------------------ */
811
/*
812
 *   Quit the game. 
813
 */
814
DefineSystemAction(Quit)
815
    execSystemAction()
816
    {
817
        /* confirm that they really want to quit */
818
        gLibMessages.confirmQuit();
819
        if (yesOrNo())
820
        {
821
            /* carry out the termination */
822
            terminateGame();
823
        }
824
        else
825
        {
826
            /* show the confirmation that we're not quitting */
827
            gLibMessages.notTerminating();
828
        }
829
    }
830
831
    /*
832
     *   Carry out game termination.  This can be called when we wish to
833
     *   end the game without asking for any additional player
834
     *   confirmation.  
835
     */
836
    terminateGame()
837
    {
838
        /* acknowledge that we're quitting */
839
        gLibMessages.okayQuitting();
840
            
841
        /* throw a 'quitting' signal to end the game */
842
        throw new QuittingException;
843
    }
844
845
    /* there's no point in including this in undo */
846
    includeInUndo = nil
847
;
848
849
/*
850
 *   Pause the game.  This stops the real-time clock until the user
851
 *   presses a key.  Games that don't use the real-time clock will have no
852
 *   use for this. 
853
 */
854
DefineSystemAction(Pause)
855
    execSystemAction()
856
    {
857
        local elapsed;
858
        
859
        /* 
860
         *   remember the current elapsed game real time - when we are
861
         *   released from the pause, we'll restore this time 
862
         */
863
        elapsed = realTimeManager.getElapsedTime();
864
865
        /* show our prompt */
866
        gLibMessages.pausePrompt();
867
868
        /* keep going until we're released */
869
    waitLoop:
870
        for (;;)
871
        {
872
            /* 
873
             *   Wait for a key, and see what we have.  Note that we
874
             *   explicitly do not want to allow any real-time events to
875
             *   occur, so we simply wait forever without timeout. 
876
             */
877
            switch(inputKey())
878
            {
879
            case ' ':
880
                /* space key - end the wait */
881
                break waitLoop;
882
883
            case 's':
884
            case 'S':
885
                /* mention that we're saving */
886
                gLibMessages.pauseSaving();
887
                
888
                /* 
889
                 *   set the elapsed time to the time when we started, so
890
                 *   that the saved position reflects the time at the
891
                 *   start of the pause 
892
                 */
893
                realTimeManager.setElapsedTime(elapsed);
894
895
                /* save the game - go run the normal SAVE command */
896
                SaveAction.execSystemAction();
897
898
                /* show our prompt again */
899
                "<.p>";
900
                gLibMessages.pausePrompt();
901
902
                /* go back to wait for another key */
903
                break;
904
                
905
            case '[eof]':
906
                /* end-of-file on keyboard input - throw an error */
907
                "\b";
908
                throw new EndOfFileException();
909
910
            default:
911
                /* ignore other keys; just go back to wait again */
912
                break;
913
            }
914
        }
915
916
        /* show the released-from-pause message */
917
        gLibMessages.pauseEnded();
918
919
        /* 
920
         *   set the real-time clock to the same elapsed game time
921
         *   that we had when we started the pause, so that the
922
         *   elapsed real time of the pause itself doesn't count
923
         *   against the game elapsed time 
924
         */
925
        realTimeManager.setElapsedTime(elapsed);
926
    }
927
;
928
929
/*
930
 *   Change to VERBOSE mode. 
931
 */
932
DefineSystemAction(Verbose)
933
    execSystemAction()
934
    {
935
        /* set the global 'verbose' mode */
936
        gameMain.verboseMode.isOn = true;
937
938
        /* acknowledge it */
939
        gLibMessages.acknowledgeVerboseMode(true);
940
    }
941
;
942
943
/*
944
 *   Change to TERSE mode. 
945
 */
946
DefineSystemAction(Terse)
947
    execSystemAction()
948
    {
949
        /* set the global 'verbose' mode */
950
        gameMain.verboseMode.isOn = nil;
951
952
        /* acknowledge it */
953
        gLibMessages.acknowledgeVerboseMode(nil);
954
    }
955
;
956
957
/* in case the score module isn't present */
958
property showScore;
959
property showFullScore;
960
property scoreNotify;
961
962
/*
963
 *   Show the current score.
964
 */
965
DefineSystemAction(Score)
966
    execSystemAction()
967
    {
968
        /* show the simple score */
969
        if (libGlobal.scoreObj != nil)
970
        {
971
            /* show the score */
972
            libGlobal.scoreObj.showScore();
973
974
            /* 
975
             *   Mention the FULL SCORE command to the player if we haven't
976
             *   already.  Note that we only want to mention 
977
             */
978
            if (!mentionedFullScore)
979
            {
980
                /* explain about it */
981
                gLibMessages.mentionFullScore;
982
983
                /* don't mention it again */
984
                ScoreAction.mentionedFullScore = true;
985
            }
986
        }
987
        else
988
            gLibMessages.scoreNotPresent;
989
    }
990
991
    /* there's no point in including this in undo */
992
    includeInUndo = nil
993
994
    /* have we mentioned the FULL SCORE command yet? */
995
    mentionedFullScore = nil
996
;
997
998
/*
999
 *   Show the full score. 
1000
 */
1001
DefineSystemAction(FullScore)
1002
    execSystemAction()
1003
    {
1004
        /* show the full score in response to an explicit player request */
1005
        showFullScore();
1006
1007
        /* this counts as a mention of the FULL SCORE command */
1008
        ScoreAction.mentionedFullScore = true;
1009
    }
1010
1011
    /* show the full score */
1012
    showFullScore()
1013
    {
1014
        /* show the full score */
1015
        if (libGlobal.scoreObj != nil)
1016
            libGlobal.scoreObj.showFullScore();
1017
        else
1018
            gLibMessages.scoreNotPresent;
1019
    }
1020
1021
    /* there's no point in including this in undo */
1022
    includeInUndo = nil
1023
;
1024
1025
/*
1026
 *   Show the NOTIFY status. 
1027
 */
1028
DefineSystemAction(Notify)
1029
    execSystemAction()
1030
    {
1031
        /* show the current notification status */
1032
        if (libGlobal.scoreObj != nil)
1033
            gLibMessages.showNotifyStatus(
1034
                libGlobal.scoreObj.scoreNotify.isOn);
1035
        else
1036
            gLibMessages.commandNotPresent;
1037
    }
1038
;
1039
1040
/*
1041
 *   Turn score change notifications on. 
1042
 */
1043
DefineSystemAction(NotifyOn)
1044
    execSystemAction()
1045
    {
1046
        /* turn notifications on, and acknowledge the status */
1047
        if (libGlobal.scoreObj != nil)
1048
        {
1049
            libGlobal.scoreObj.scoreNotify.isOn = true;
1050
            gLibMessages.acknowledgeNotifyStatus(true);
1051
        }
1052
        else
1053
            gLibMessages.commandNotPresent;
1054
    }
1055
;
1056
1057
/*
1058
 *   Turn score change notifications off. 
1059
 */
1060
DefineSystemAction(NotifyOff)
1061
    execSystemAction()
1062
    {
1063
        /* turn notifications off, and acknowledge the status */
1064
        if (libGlobal.scoreObj != nil)
1065
        {
1066
            libGlobal.scoreObj.scoreNotify.isOn = nil;
1067
            gLibMessages.acknowledgeNotifyStatus(nil);
1068
        }
1069
        else
1070
            gLibMessages.commandNotPresent;
1071
    }
1072
;
1073
1074
/*
1075
 *   Show version information for the game and the library modules the
1076
 *   game is using.  
1077
 */
1078
DefineSystemAction(Version)
1079
    execSystemAction()
1080
    {
1081
        /* show the version information for each library */
1082
        foreach (local cur in ModuleID.getModuleList())
1083
            cur.showVersion();
1084
    }
1085
1086
    /* there's no point in including this in undo */
1087
    includeInUndo = nil
1088
;
1089
1090
/*
1091
 *   Show the credits for the game and the library modules the game
1092
 *   includes. 
1093
 */
1094
DefineSystemAction(Credits)
1095
    execSystemAction()
1096
    {
1097
        /* show the credits for each library */
1098
        foreach (local cur in ModuleID.getModuleList())
1099
            cur.showCredit();
1100
    }
1101
1102
    /* there's no point in including this in undo */
1103
    includeInUndo = nil
1104
;
1105
1106
/*
1107
 *   Show the "about" information for the game and library modules.
1108
 */
1109
DefineSystemAction(About)
1110
    execSystemAction()
1111
    {
1112
        local anyOutput;
1113
        
1114
        /* watch for any output while showing module information */
1115
        anyOutput = outputManager.curOutputStream
1116
                    .watchForOutput(new function()
1117
        {
1118
            /* show information for each module */
1119
            foreach (local cur in ModuleID.getModuleList())
1120
                cur.showAbout();
1121
        });
1122
1123
        /* 
1124
         *   if we didn't have any ABOUT information to show, display a
1125
         *   message to this effect 
1126
         */
1127
        if (!anyOutput)
1128
            gLibMessages.noAboutInfo;
1129
    }
1130
1131
    /* there's no point in including this in undo */
1132
    includeInUndo = nil
1133
;
1134
1135
/*
1136
 *   A state object that keeps track of our logging (scripting) status.
1137
 *   This is transient, because logging is controlled through the output
1138
 *   layer in the interpreter, which does not participate in any of the
1139
 *   persistence mechanisms.  
1140
 */
1141
transient scriptStatus: object
1142
    /*
1143
     *   Script file name.  This is nil when logging is not in effect, and
1144
     *   is set to the name of the scripting file when a log file is
1145
     *   active. 
1146
     */
1147
    scriptFile = nil
1148
1149
    /* RECORD file name */
1150
    recordFile = nil
1151
1152
    /* have we warned about using NOTE without logging in effect? */
1153
    noteWithoutScriptWarning = nil
1154
;
1155
1156
/*
1157
 *   A base class for file-oriented actions, such as SCRIPT, RECORD, and
1158
 *   REPLAY.  We provide common handling that prompts interactively for a
1159
 *   filename; subclasses must override a few methods and properties to
1160
 *   carry out the specific subclassed operation on the file.  
1161
 */
1162
DefineSystemAction(FileOp)
1163
    /* our file dialog prompt message */
1164
    filePromptMsg = ''
1165
1166
    /* the file dialog open/save type */
1167
    fileDisposition = InFileSave
1168
1169
    /* the file dialog type ID */
1170
    fileTypeID = FileTypeLog
1171
1172
    /* show our cancellation mesage */
1173
    showCancelMsg = ""
1174
1175
    /* carry out our file operation */
1176
    performFileOp(fname, ack)
1177
    {
1178
        /* 
1179
         *   Each concrete action subclass must override this to carry out
1180
         *   our operation.  This is called when the user has successfully
1181
         *   selected a filename for the operatoin.  
1182
         */
1183
    }
1184
1185
    execSystemAction()
1186
    {
1187
        /* 
1188
         *   ask for a file and carry out our action; since the command is
1189
         *   being performed directly from the command line, we want an
1190
         *   acknowledgment message on success 
1191
         */
1192
        setUpFileOp(true);
1193
    }
1194
1195
    /* ask for a file, and carry out our operation is we get one */
1196
    setUpFileOp(ack)
1197
    {
1198
        local result;
1199
        local origElapsedTime;
1200
1201
        /* note the current game time */
1202
        origElapsedTime = realTimeManager.getElapsedTime();
1203
1204
        /* ask for a file */
1205
        result = getInputFile(filePromptMsg, fileDisposition, fileTypeID, 0);
1206
1207
        /* check the inputFile result */
1208
        switch(result[1])
1209
        {
1210
        case InFileSuccess:
1211
            /* carry out our file operation */
1212
            performFileOp(result[2], ack);
1213
            break;
1214
1215
        case InFileFailure:
1216
            /* advise of the failure of the prompt */
1217
            gLibMessages.filePromptFailed();
1218
            break;
1219
1220
        case InFileCancel:
1221
            /* acknowledge the cancellation */
1222
            showCancelMsg();
1223
            break;
1224
        }
1225
1226
        /* 
1227
         *   restore the original elapsed game time, so that the time spent
1228
         *   in the file selector dialog doesn't count against the game
1229
         *   time 
1230
         */
1231
        realTimeManager.setElapsedTime(origElapsedTime);
1232
    }
1233
1234
    /* we can't include this in undo, as it affects external files */
1235
    includeInUndo = nil
1236
;
1237
1238
/*
1239
 *   Turn scripting on.  This creates a text file that contains a
1240
 *   transcript of all commands and responses from this point forward.
1241
 */
1242
DefineAction(Script, FileOpAction)
1243
    /* our file dialog parameters - ask for a log file to save */
1244
    filePromptMsg = (gLibMessages.getScriptingPrompt())
1245
    fileTypeID = FileTypeLog
1246
    fileDisposition = InFileSave
1247
1248
    /* show our cancellation mesasge */
1249
    showCancelMsg() { gLibMessages.scriptingCanceled(); }
1250
1251
    /* 
1252
     *   set up scripting - this can be used to set up scripting
1253
     *   programmatically, in the course of carrying out another action 
1254
     */
1255
    setUpScripting(ack) { setUpFileOp(ack); }
1256
1257
    /* turn on scripting to the given file */
1258
    performFileOp(fname, ack)
1259
    {
1260
        /* turn on logging */
1261
        setLogFile(fname, LogTypeTranscript);
1262
1263
        /* remember that scripting is in effect */
1264
        scriptStatus.scriptFile = fname;
1265
1266
        /* 
1267
         *   forget any past warning that we've issued about NOTE without
1268
         *   a script in effect; the next time scripting isn't active,
1269
         *   we'll want to issue a new warning, since they might not be
1270
         *   aware at that point that the scripting we're starting now has
1271
         *   ended 
1272
         */
1273
        scriptStatus.noteWithoutScriptWarning = nil;
1274
1275
        /* note that logging is active, if acknowledgment is desired */
1276
        if (ack)
1277
            gLibMessages.scriptingOkay();
1278
    }
1279
;
1280
1281
/*
1282
 *   Subclass of Script action taking a quoted string as part of the
1283
 *   command syntax.  The grammar rule must set our fname_ property to a
1284
 *   quotedStringPhrase subproduction. 
1285
 */
1286
DefineAction(ScriptString, ScriptAction)
1287
    execSystemAction()
1288
    {
1289
        /* if there's a filename, we don't need to prompt */
1290
        if (fname_ != nil)
1291
        {
1292
            /* set up scripting to the filename specified in the command */
1293
            performFileOp(fname_.getStringText(), true);
1294
        }
1295
        else
1296
        {
1297
            /* there's no filename, so prompt as usual */
1298
            inherited();
1299
        }
1300
    }
1301
;
1302
1303
/*
1304
 *   Turn scripting off.  This stops recording the game transcript started
1305
 *   with the most recent SCRIPT command. 
1306
 */
1307
DefineSystemAction(ScriptOff)
1308
    execSystemAction()
1309
    {
1310
        /* turn off scripting */
1311
        turnOffScripting(true);
1312
    }
1313
1314
    /* turn off scripting */
1315
    turnOffScripting(ack)
1316
    {
1317
        /* if we're not in a script file, ignore it */
1318
        if (scriptStatus.scriptFile == nil)
1319
        {
1320
            gLibMessages.scriptOffIgnored();
1321
            return;
1322
        }
1323
1324
        /* cancel scripting in the interpreter's output layer */
1325
        setLogFile(nil, LogTypeTranscript);
1326
1327
        /* remember that scripting is no longer in effect */
1328
        scriptStatus.scriptFile = nil;
1329
1330
        /* acknowledge the change, if desired */
1331
        if (ack)
1332
            gLibMessages.scriptOffOkay();
1333
    }
1334
1335
    /* we can't include this in undo, as it affects external files */
1336
    includeInUndo = nil
1337
;
1338
1339
/*
1340
 *   RECORD - this is similar to SCRIPT, but stores a file containing only
1341
 *   the command input, not the output. 
1342
 */
1343
DefineAction(Record, FileOpAction)
1344
    /* our file dialog parameters - ask for a log file to save */
1345
    filePromptMsg = (gLibMessages.getRecordingPrompt())
1346
    fileTypeID = FileTypeCmd
1347
    fileDisposition = InFileSave
1348
1349
    /* show our cancellation mesasge */
1350
    showCancelMsg() { gLibMessages.recordingCanceled(); }
1351
1352
    /* 
1353
     *   set up recording - this can be used to set up scripting
1354
     *   programmatically, in the course of carrying out another action 
1355
     */
1356
    setUpRecording(ack) { setUpFileOp(ack); }
1357
1358
    /* turn on recording to the given file */
1359
    performFileOp(fname, ack)
1360
    {
1361
        /* turn on command logging */
1362
        setLogFile(fname, logFileType);
1363
1364
        /* remember that recording is in effect */
1365
        scriptStatus.recordFile = fname;
1366
1367
        /* note that logging is active, if acknowledgment is desired */
1368
        if (ack)
1369
            gLibMessages.recordingOkay();
1370
    }
1371
1372
    /* the log file type - by default, we open a regular command log */
1373
    logFileType = LogTypeCommand
1374
;
1375
1376
/* subclass of Record action that sets up an event script recording */
1377
DefineAction(RecordEvents, RecordAction)
1378
    logFileType = LogTypeScript
1379
;
1380
1381
/* subclass of Record action taking a quoted string for the filename */
1382
DefineAction(RecordString, RecordAction)
1383
    execSystemAction()
1384
    {
1385
        /* set up scripting to the filename specified in the command */
1386
        performFileOp(fname_.getStringText(), true);
1387
    }
1388
;
1389
1390
/* subclass of RecordString action that sets up an event script recording */
1391
DefineAction(RecordEventsString, RecordStringAction)
1392
    logFileType = LogTypeScript
1393
;
1394
1395
/*
1396
 *   Turn command recording off.  This stops recording the command log
1397
 *   started with the most recent RECORD command.  
1398
 */
1399
DefineSystemAction(RecordOff)
1400
    execSystemAction()
1401
    {
1402
        /* turn off recording */
1403
        turnOffRecording(true);
1404
    }
1405
1406
    /* turn off recording */
1407
    turnOffRecording(ack)
1408
    {
1409
        /* if we're not recording anything, ignore it */
1410
        if (scriptStatus.recordFile == nil)
1411
        {
1412
            gLibMessages.recordOffIgnored();
1413
            return;
1414
        }
1415
1416
        /* cancel recording in the interpreter's output layer */
1417
        setLogFile(nil, LogTypeCommand);
1418
1419
        /* remember that recording is no longer in effect */
1420
        scriptStatus.recordFile = nil;
1421
1422
        /* acknowledge the change, if desired */
1423
        if (ack)
1424
            gLibMessages.recordOffOkay();
1425
    }
1426
1427
    /* we can't include this in undo, as it affects external files */
1428
    includeInUndo = nil
1429
;
1430
1431
/*
1432
 *   REPLAY - play back a command log previously recorded. 
1433
 */
1434
DefineAction(Replay, FileOpAction)
1435
    /* our file dialog parameters - ask for a log file to save */
1436
    filePromptMsg = (gLibMessages.getReplayPrompt())
1437
    fileTypeID = FileTypeCmd
1438
    fileDisposition = InFileOpen
1439
1440
    /* show our cancellation mesasge */
1441
    showCancelMsg() { gLibMessages.replayCanceled(); }
1442
1443
    /* script flags passed to setScriptFile */
1444
    scriptOptionFlags = 0
1445
1446
    /* replay the given file */
1447
    performFileOp(fname, ack)
1448
    {
1449
        /* 
1450
         *   Note that we're reading from the script file if desired.  Do
1451
         *   this before opening the script, so that we display the
1452
         *   acknowledgment even if we're in 'quiet' mode. 
1453
         */
1454
        if (ack)
1455
            gLibMessages.inputScriptOkay(fname);
1456
1457
        /* activate the script file */
1458
        setScriptFile(fname, scriptOptionFlags);
1459
    }
1460
;
1461
1462
/* subclass of Replay action taking a quoted string for the filename */
1463
DefineAction(ReplayString, ReplayAction)
1464
    execSystemAction()
1465
    {
1466
        /* 
1467
         *   if there's a string, use the string as the filename;
1468
         *   otherwise, inherit the default handling to ask for a filename 
1469
         */
1470
        if (fname_ != nil)
1471
        {
1472
            /* set up scripting to the filename specified in the command */
1473
            performFileOp(fname_.getStringText(), true);
1474
        }
1475
        else
1476
        {
1477
            /* inherit the default handling to ask for a filename */
1478
            inherited();
1479
        }
1480
    }
1481
;
1482
1483
1484
/* in case the footnote module is not present */
1485
property showFootnote;
1486
1487
/*
1488
 *   Footnote - this requires a numeric argument parsed via the
1489
 *   numberPhrase production and assigned to the numMatch property.  
1490
 */
1491
DefineSystemAction(Footnote)
1492
    execSystemAction()
1493
    {
1494
        /* ask the Footnote class to do the work */
1495
        if (libGlobal.footnoteClass != nil)
1496
            libGlobal.footnoteClass.showFootnote(numMatch.getval());
1497
        else
1498
            gLibMessages.commandNotPresent;
1499
    }
1500
1501
    /* there's no point in including this in undo */
1502
    includeInUndo = nil
1503
;
1504
1505
property footnoteSettings;
1506
1507
/* base class for FOOTNOTES xxx commands */
1508
DefineSystemAction(Footnotes)
1509
    execSystemAction()
1510
    {
1511
        if (libGlobal.footnoteClass != nil)
1512
        {
1513
            /* set my footnote status in the global setting */
1514
            libGlobal.footnoteClass.footnoteSettings.showFootnotes =
1515
                showFootnotes;
1516
1517
            /* acknowledge it */
1518
            gLibMessages.acknowledgeFootnoteStatus(showFootnotes);
1519
        }
1520
        else
1521
            gLibMessages.commandNotPresent;
1522
    }
1523
1524
    /* 
1525
     *   the footnote status I set when this command is activated - this
1526
     *   must be overridden by each subclass 
1527
     */
1528
    showFootnotes = nil
1529
;
1530
1531
DefineAction(FootnotesFull, FootnotesAction)
1532
    showFootnotes = FootnotesFull
1533
;
1534
1535
DefineAction(FootnotesMedium, FootnotesAction)
1536
    showFootnotes = FootnotesMedium
1537
;
1538
1539
DefineAction(FootnotesOff, FootnotesAction)
1540
    showFootnotes = FootnotesOff
1541
;
1542
1543
DefineSystemAction(FootnotesStatus)
1544
    execSystemAction()
1545
    {
1546
        /* show the current status */
1547
        if (libGlobal.footnoteClass != nil)
1548
            gLibMessages.showFootnoteStatus(libGlobal.footnoteClass.
1549
                                            footnoteSettings.showFootnotes);
1550
        else
1551
            gLibMessages.commandNotPresent;
1552
    }
1553
1554
    /* there's no point in including this in undo */
1555
    includeInUndo = nil
1556
;
1557
1558
DefineIAction(Inventory)
1559
    execAction()
1560
    {
1561
        /* show the actor's inventory in the current mode */
1562
        gActor.showInventory(inventoryMode == InventoryTall);
1563
    }
1564
1565
    /* current inventory mode - start in 'wide' mode by default */
1566
    inventoryMode = InventoryWide;
1567
;
1568
1569
DefineIAction(InventoryTall)
1570
    execAction()
1571
    {
1572
        /* set inventory mode to 'tall' */
1573
        InventoryAction.inventoryMode = InventoryTall;
1574
1575
        /* run the inventory action */
1576
        InventoryAction.checkAction();
1577
        InventoryAction.execAction();
1578
    }
1579
;
1580
1581
DefineIAction(InventoryWide)
1582
    execAction()
1583
    {
1584
        /* set inventory mode to 'wide' */
1585
        InventoryAction.inventoryMode = InventoryWide;
1586
1587
        /* run the inventory action */
1588
        InventoryAction.checkAction();
1589
        InventoryAction.execAction();
1590
    }
1591
;
1592
1593
DefineIAction(Wait)
1594
    execAction()
1595
    {
1596
        /* just show the "time passes" message */
1597
        defaultReport(&timePassesMsg);
1598
    }
1599
;
1600
1601
DefineIAction(Look)
1602
    execAction()
1603
    {
1604
        /* show the actor's current location in verbose mode */
1605
        gActor.lookAround(true);
1606
    }
1607
;
1608
1609
DefineIAction(Sleep)
1610
    execAction()
1611
    {
1612
        /* let the actor handle it */
1613
        gActor.goToSleep();
1614
    }
1615
;
1616
1617
DefineTAction(Take)
1618
    /* this is a basic inventory-management verb, so allow ALL with it */
1619
    actionAllowsAll = true
1620
1621
    /* get the ALL list for the direct object */
1622
    getAllDobj(actor, scopeList)
1623
    {
1624
        local locList;
1625
        local dropLoc;
1626
        local actorLoc;
1627
        
1628
        /*
1629
         *   Include all of the objects that are directly in the actor's
1630
         *   immediate container, the container's container, and so on out
1631
         *   to the "drop destination" location (which is where things go
1632
         *   when we DROP them, and is meant to represent the nearest
1633
         *   platform-like or floor-like container).  Also include anything
1634
         *   that's directly in anything fixed in place within one of these
1635
         *   containers.  Don't include anything that actually contains the
1636
         *   actor, since we normally can't pick up something we're inside.
1637
         *   
1638
         *   Start by getting the actor's immediate location and drop
1639
         *   destination location.  
1640
         */
1641
        actorLoc = actor.location;
1642
        dropLoc = actor.getDropDestination(nil, nil);
1643
1644
        /* 
1645
         *   create a vector to hold the location list, and start it off
1646
         *   with the drop location 
1647
         */
1648
        locList = new Vector(10);
1649
        locList.append(dropLoc);
1650
1651
        /* now work up the location list until we hit the drop location */
1652
        for (local cur = actorLoc ; cur != nil && cur != dropLoc ;
1653
             cur = cur.location)
1654
        {
1655
            /* add this container to the list */
1656
            locList.append(cur);
1657
        }
1658
1659
        /* 
1660
         *   now generate the subset of in-scope objects that are directly
1661
         *   in any of these locations (or directly in items fixed in place
1662
         *   within any of these locations), and return the result 
1663
         */
1664
        return scopeList.subset(
1665
            {x: (locList.indexWhich(
1666
                {loc: x.isDirectlyIn(loc) || x.isInFixedIn(loc)}) != nil
1667
                 && !actor.isIn(x)) });
1668
    }
1669
;
1670
1671
DefineTIAction(TakeFrom)
1672
    /* this is a basic inventory-management verb, so allow ALL with it */
1673
    actionAllowsAll = true
1674
1675
    /* get the ALL list for the direct object */
1676
    getAllDobj(actor, scopeList)
1677
    {
1678
        /* ask the indirect object for the list */
1679
        return getIobj() == nil
1680
            ? []
1681
            : getIobj().getAllForTakeFrom(scopeList);
1682
    }
1683
;
1684
1685
DefineTAction(Remove)
1686
;
1687
1688
DefineTAction(Drop)
1689
    /* this is a basic inventory-management verb, so allow ALL with it */
1690
    actionAllowsAll = true
1691
1692
    /* get the ALL list for the direct object */
1693
    getAllDobj(actor, scopeList)
1694
    {
1695
        /* include only objects directly held by the actor */
1696
        return scopeList.subset({x: x.isDirectlyIn(actor)});
1697
    }
1698
;
1699
1700
DefineTAction(Examine)
1701
;
1702
1703
DefineTAction(Read)
1704
;
1705
1706
DefineTAction(LookIn)
1707
;
1708
1709
DefineTAction(Search)
1710
;
1711
1712
DefineTAction(LookUnder)
1713
;
1714
1715
DefineTAction(LookBehind)
1716
;
1717
1718
DefineTAction(LookThrough)
1719
;
1720
1721
DefineTAction(Feel)
1722
;
1723
1724
DefineTAction(Taste)
1725
;
1726
1727
DefineTAction(Smell)
1728
;
1729
1730
DefineTAction(ListenTo)
1731
;
1732
1733
/*
1734
 *   Base class for undirected sensing, such as "listen" or "smell" with no
1735
 *   object.  We'll scan for things that have a presence in the
1736
 *   corresponding sense and describe each one.  
1737
 */
1738
DefineIAction(SenseImplicit)
1739
    /* the sense in which I operate */
1740
    mySense = nil
1741
1742
    /* the object property to display this sense's description */
1743
    descProp = nil
1744
    
1745
    /* the default message to display if we find nothing specific to sense */
1746
    defaultMsgProp = nil
1747
1748
    /* the Lister we use to show the items */
1749
    resultLister = nil
1750
1751
    /* execute the action */
1752
    execAction()
1753
    {
1754
        local senseTab;
1755
        local presenceList;
1756
            
1757
        /* get a list of everything in range of this sense for the actor */
1758
        senseTab = gActor.senseInfoTable(mySense);
1759
1760
        /* get a list of everything with a presence in this sense */
1761
        presenceList = senseInfoTableSubset(senseTab,
1762
            {obj, info: obj.(mySense.presenceProp)});
1763
1764
        /* 
1765
         *   if there's anything in the list, show it; otherwise, show a
1766
         *   default report 
1767
         */
1768
        if (presenceList.length() != 0)
1769
        {
1770
            /* show the list using our lister */
1771
            resultLister.showList(gActor, nil, presenceList, 0, 0,
1772
                                  senseTab, nil);
1773
        }
1774
        else
1775
        {
1776
            /* there's nothing to show - say so */
1777
            defaultReport(defaultMsgProp);
1778
        }
1779
    }
1780
;
1781
1782
DefineAction(SmellImplicit, SenseImplicitAction)
1783
    mySense = smell
1784
    descProp = &smellDesc
1785
    defaultMsgProp = &nothingToSmellMsg
1786
    resultLister = smellActionLister
1787
;
1788
1789
DefineAction(ListenImplicit, SenseImplicitAction)
1790
    mySense = sound
1791
    descProp = &soundDesc
1792
    defaultMsgProp = &nothingToHearMsg
1793
    resultLister = listenActionLister
1794
;
1795
1796
DefineTIAction(PutIn)
1797
    /* this is a basic inventory-management verb, so allow ALL with it */
1798
    actionAllowsAll = true
1799
1800
    /* get the ALL list for the direct object */
1801
    getAllDobj(actor, scopeList)
1802
    {
1803
        local loc;
1804
        local iobj = nil;
1805
        local iobjIdent = nil;
1806
1807
        /* get the actor's location */
1808
        loc = actor.location;
1809
1810
        /* if we have an iobj list, retrieve its first element */
1811
        if (iobjList_ != nil && iobjList_.length() > 0)
1812
        {
1813
            iobj = iobjList_[1].obj_;
1814
            iobjIdent = iobj.getIdentityObject();
1815
        }
1816
        
1817
        /*
1818
         *   Include objects that are directly in the actor's location, or
1819
         *   within fixed items in the actor's location, or directly in the
1820
         *   actor's inventory.
1821
         *   
1822
         *   Exclude the indirect object and its "identity" object (since
1823
         *   we obviously can't put the indirect object in itself), and
1824
         *   exclude everything already directly in the indirect object.  
1825
         */
1826
        return scopeList.subset({x:
1827
                                (x.isDirectlyIn(loc)
1828
                                 || x.isInFixedIn(loc)
1829
                                 || x.isDirectlyIn(actor))
1830
                                && x != iobj
1831
                                && x != iobjIdent
1832
                                && !x.isDirectlyIn(iobj)});
1833
    }
1834
;
1835
1836
DefineTIAction(PutOn)
1837
    /* this is a basic inventory-management verb, so allow ALL with it */
1838
    actionAllowsAll = true
1839
1840
    /* get the ALL list for the direct object */
1841
    getAllDobj(actor, scopeList)
1842
    {
1843
        /* use the same strategy that we do in PutIn */
1844
        local loc = actor.location;
1845
        return scopeList.subset({x:
1846
                                (x.isDirectlyIn(loc)
1847
                                 || x.isInFixedIn(loc)
1848
                                 || x.isDirectlyIn(actor))
1849
                                && x != getIobj()
1850
                                && !x.isDirectlyIn(getIobj())});
1851
    }
1852
;
1853
1854
DefineTIAction(PutUnder)
1855
;
1856
1857
DefineTIAction(PutBehind)
1858
;
1859
1860
DefineTAction(Wear)
1861
;
1862
1863
DefineTAction(Doff)
1864
;
1865
1866
DefineConvTopicTAction(AskFor, IndirectObject)
1867
;
1868
1869
DefineConvTopicTAction(AskAbout, IndirectObject)
1870
;
1871
1872
DefineConvTopicTAction(TellAbout, IndirectObject)
1873
    /*
1874
     *   TELL ABOUT is a conversational address, as opposed to an order,
1875
     *   if the direct object of the action is the same as the issuer: in
1876
     *   this case, the command has the form <actor>, TELL ME ABOUT
1877
     *   <topic>, which has exactly the same meaning as ASK <actor> ABOUT
1878
     *   <topic>. 
1879
     */
1880
    isConversational(issuer)
1881
    {
1882
        local dobj;
1883
        
1884
        /* 
1885
         *   if the resolved direct object matches the issuer, it's
1886
         *   conversational 
1887
         */
1888
        dobj = getResolvedDobjList();
1889
        return (dobj.length() == 1 && dobj[1] == issuer);
1890
    }
1891
;
1892
1893
/*
1894
 *   AskVague and TellVague are for syntactically incorrect phrasings that
1895
 *   a player might accidentally type, especially in conjunction with a
1896
 *   past SpecialTopic prompt; in English, for example, we define these as
1897
 *   ASK <actor> <topic> and TELL <actor> <topic>.  These are used only to
1898
 *   provide more helpful error messages.  
1899
 */
1900
DefineTopicTAction(AskVague, IndirectObject)
1901
;
1902
DefineTopicTAction(TellVague, IndirectObject)
1903
;
1904
1905
DefineConvIAction(Hello)
1906
    execAction()
1907
    {
1908
        /* the issuing actor is saying hello to the target actor */
1909
        gIssuingActor.sayHello(gActor);
1910
    }
1911
;
1912
1913
DefineConvIAction(Goodbye)
1914
    execAction()
1915
    {
1916
        /* the issuing actor is saying goodbye to the target actor */
1917
        gIssuingActor.sayGoodbye(gActor);
1918
    }
1919
;
1920
1921
DefineConvIAction(Yes)
1922
    execAction()
1923
    {
1924
        /* the issuing actor is saying yes to the target actor */
1925
        gIssuingActor.sayYes(gActor);
1926
    }
1927
;
1928
1929
DefineConvIAction(No)
1930
    execAction()
1931
    {
1932
        /* the issuing actor is saying no to the target actor */
1933
        gIssuingActor.sayNo(gActor);
1934
    }
1935
;
1936
1937
/*
1938
 *   Invoke the active SpecialTopic.  This isn't a real command - the
1939
 *   player will never actually type this; rather, it's a pseudo-command
1940
 *   that we send to ourselves from a string pre-parser when we recognize
1941
 *   input that matches a SpecialTopic's custom command syntax.
1942
 *   
1943
 *   Note that we actually define the syntax for this command right here
1944
 *   in the language-independent library, because this isn't a real
1945
 *   command.  The user never needs to type this command, since it's
1946
 *   something we generate internally.  The only important language issue
1947
 *   is that we use a command keyword that no language will ever want to
1948
 *   use for a real command, so we intentionally use some near-English
1949
 *   gibberish.  
1950
 */
1951
DefineLiteralAction(SpecialTopic)
1952
    execAction()
1953
    {
1954
        /* 
1955
         *   the issuing actor is saying the current special topic to the
1956
         *   actor's current interlocutor 
1957
         */
1958
        gIssuingActor.saySpecialTopic();
1959
    }
1960
1961
    /* 
1962
     *   Repeat the action, for an AGAIN command.  We need to make sure
1963
     *   the special text interpretation we gave to the command still
1964
     *   holds; if not, reparse the original text and try that.  
1965
     */
1966
    repeatAction(lastTargetActor, lastTargetActorPhrase,
1967
                 lastIssuingActor, countsAsIssuerTurn)
1968
    {
1969
        local cmd;
1970
1971
        /* get the original text the player entered */
1972
        cmd = getEnteredText();
1973
        
1974
        /* 
1975
         *   try running this through the special topic pre-parser again,
1976
         *   to see if it still has the special meaning 
1977
         */
1978
        if (specialTopicPreParser.doParsing(cmd, rmcCommand)
1979
            .startsWith('xspcltopic '))
1980
        {
1981
            /* 
1982
             *   it still has the special meaning, so simply execute as we
1983
             *   normally would, by inheriting the standard Action
1984
             *   handling 
1985
             */
1986
            inherited(lastTargetActor, lastTargetActorPhrase,
1987
                      lastIssuingActor, countsAsIssuerTurn);
1988
        }
1989
        else
1990
        {
1991
            /*
1992
             *   The command no longer has the special meaning it did on
1993
             *   the last command, so we can't repeat this command.  
1994
             */
1995
            gLibMessages.againNotPossible(lastIssuingActor);
1996
        }
1997
    }
1998
1999
    /*
2000
     *   Get the original player-entered text.  This is our literal
2001
     *   phrase, with the embedded-quote encoding decoded.
2002
     */
2003
    getEnteredText() { return decodeOrig(getLiteral()); }
2004
2005
    /* 
2006
     *   encode the original text for our literal phrase: turn double
2007
     *   quotes into '%q' sequences, and turn percent signs into '%%'
2008
     *   sequences 
2009
     */
2010
    encodeOrig(txt)
2011
    {
2012
        /* first, replace any percent signs with '%%' sequences */
2013
        txt = txt.findReplace('%', '%%', ReplaceAll);
2014
2015
        /* next, replace any double quotes with '%q' sequences */
2016
        txt = txt.findReplace('"', '%q', ReplaceAll);
2017
2018
        /* return the text */
2019
        return txt;
2020
    }
2021
2022
    /* decode our encoding */
2023
    decodeOrig(txt)
2024
    {
2025
        /* find each '%' sequence and decode it */
2026
        for (local idx = 1 ; idx <= txt.length() ; )
2027
        {
2028
            /* find the next '%' */
2029
            idx = txt.find('%', idx);
2030
2031
            /* if we found nothing, we're done */
2032
            if (idx == nil)
2033
                break;
2034
2035
            /* 
2036
             *   if it's the last character, ignore it, as all of our
2037
             *   encodings are two characters long 
2038
             */
2039
            if (idx == txt.length())
2040
                break;
2041
2042
            /* check what we have next */
2043
            switch (txt.substr(idx + 1, 1))
2044
            {
2045
            case 'q':
2046
                /* it's a quote */
2047
                txt = txt.findReplace('%q', '"', ReplaceOnce, idx);
2048
                break;
2049
2050
            case '%':
2051
                /* it's a single percent sign */
2052
                txt = txt.findReplace('%%', '%', ReplaceOnce, idx);
2053
                break;
2054
            }
2055
2056
            /* continue searching from the next character */
2057
            ++idx;
2058
        }
2059
2060
        /* return the result */
2061
        return txt;
2062
    }
2063
;
2064
2065
grammar predicate(SpecialTopic):
2066
    'xspcltopic' literalPhrase->literalMatch
2067
    : SpecialTopicAction
2068
2069
    /*
2070
     *   Use the text of the command as originally typed by the player as
2071
     *   our apparent original text.  
2072
     */
2073
    getOrigText() { return getEnteredText(); }
2074
;
2075
2076
/* in case they try typing just 'xspcltopic' */
2077
grammar predicate(EmptySpecialTopic):
2078
    'xspcltopic' : IAction
2079
2080
    /* just act like we don't understand this command */
2081
    execAction() { gLibMessages.commandNotPresent; }
2082
;
2083
2084
DefineTAction(Kiss)
2085
;
2086
2087
DefineIAction(Yell)
2088
    execAction()
2089
    {
2090
        /* yelling generally has no effect; issue a default response */
2091
        mainReport(&okayYellMsg);
2092
    }
2093
;
2094
2095
DefineTAction(TalkTo)
2096
;
2097
2098
DefineSystemAction(Topics)
2099
    execSystemAction()
2100
    {
2101
        /* check to see if any suggestions are defined in the entire game */
2102
        if (firstObj(SuggestedTopic, ObjInstances) != nil)
2103
        {
2104
            /* we have topics - let the actor handle it */
2105
            gActor.suggestTopics(true);
2106
        }
2107
        else
2108
        {
2109
            /* there are no topics at all, so this command isn't used */
2110
            gLibMessages.commandNotPresent;
2111
        }
2112
    }
2113
2114
    /* don't include this in undo */
2115
    includeInUndo = nil
2116
;
2117
2118
DefineTIAction(GiveTo)
2119
    getDefaultIobj(np, resolver)
2120
    {
2121
        local obj;
2122
2123
        /* check the actor for a current interlocutor */
2124
        obj = resolver.getTargetActor().getCurrentInterlocutor();
2125
        if (obj != nil)
2126
            return [new ResolveInfo(obj, 0)];
2127
        else
2128
            return inherited(np, resolver);
2129
    }
2130
;
2131
2132
/*
2133
 *   Define a global remapping to transform commands of the form "X, GIVE
2134
 *   ME Y" to the format "ME, ASK X FOR Y".  This makes it easier to write
2135
 *   the code to handle these sorts of exchanges, since it means you only
2136
 *   have to write it in the ASK FOR handler.  
2137
 */
2138
giveMeToAskFor: GlobalRemapping
2139
    /*
2140
     *   Remap a command, if applicable.  We look for commands of the form
2141
     *   "X, GIVE ME Y": we look for a GiveTo action whose indirect object
2142
     *   is the same as the issuing actor.  When we find this form of
2143
     *   command, we rewrite it to "ME, ASK X FOR Y".  
2144
     */
2145
    getRemapping(issuingActor, targetActor, action)
2146
    {
2147
        /* 
2148
         *   if it's of the form "X, GIVE Y TO Z", where Z is the issuing
2149
         *   actor (generally ME, but it could conceivably be someone
2150
         *   else), transform it into "Z, ASK X FOR Y".  
2151
         */
2152
        if (action.ofKind(GiveToAction)
2153
            && action.canIobjResolveTo(issuingActor))
2154
        {
2155
            /* create the ASK FOR action */
2156
            local newAction = AskForAction.createActionInstance();
2157
2158
            /* remember the original version of the action */
2159
            newAction.setOriginalAction(action);
2160
2161
            /*
2162
             *   Changing the phrasing from "X, GIVE Y TO Z" to "Z, ASK X
2163
             *   FOR Y" will change the target actor from X in the old
2164
             *   version to Z in the new version.  In the original format,
2165
             *   the pronouns "you", "your", and "yours" implicitly refers
2166
             *   to Z ("Bob, give me your book" implies "bob's book").  The
2167
             *   rewrite will change that, though - assuming that Z is a
2168
             *   second-person actor, "you" will by default refer to Z in
2169
             *   the rewrite.  In order to preserve the original meaning,
2170
             *   we have to override "you" in the rewrite so that it
2171
             *   continues to refer to "X", which we can do using a pronoun
2172
             *   override in the new action.  
2173
             */
2174
            newAction.setPronounOverride(PronounYou, targetActor);
2175
2176
            /* 
2177
             *   The direct object - the person we're asking - is the
2178
             *   original target actor ("bob" in "bob, give me x").  Since
2179
             *   this is a specific object, we need to wrap it in a
2180
             *   PreResolvedProd.
2181
             */
2182
            local dobj = new PreResolvedProd(targetActor);
2183
2184
            /*
2185
             *   The thing we're asking for is the original direct object.
2186
             *   ASK FOR takes a topic phrase for its indirect object,
2187
             *   whereas GIVE TO takes a regular noun phrase.  The two
2188
             *   aren't quite identical syntactically, so we'll do better
2189
             *   if we re-parse the original dobj noun phrase as a topic
2190
             *   phrase.  Fortunately, this is easy...
2191
             */
2192
            local iobj = newAction.reparseMatchAsTopic(
2193
                action.dobjMatch, issuingActor, issuingActor);
2194
2195
            /* set the object match trees */
2196
            newAction.setObjectMatches(dobj, iobj);
2197
            
2198
            /* 
2199
             *   Return the new command, addressing the *issuing* actor
2200
             *   this time around. 
2201
             */
2202
            return [issuingActor, newAction];
2203
        }
2204
2205
        /* it's not ours */
2206
        return nil;
2207
    }
2208
;
2209
2210
2211
DefineTIAction(ShowTo)
2212
    getDefaultIobj(np, resolver)
2213
    {
2214
        local obj;
2215
2216
        /* check the actor for a current interlocutor */
2217
        obj = resolver.getTargetActor().getCurrentInterlocutor();
2218
        if (obj != nil)
2219
            return [new ResolveInfo(obj, 0)];
2220
        else
2221
            return inherited(np, resolver);
2222
    }
2223
;
2224
2225
DefineTAction(Follow)
2226
    /*
2227
     *   For resolving our direct object, we want to include in the scope
2228
     *   any item that isn't present but which the actor saw departing the
2229
     *   present location.  
2230
     */
2231
    initResolver(issuingActor, targetActor)
2232
    {
2233
        /* inherit the base resolver initialization */
2234
        inherited(issuingActor, targetActor);
2235
2236
        /* 
2237
         *   add to the scope all of the actor's followable objects -
2238
         *   these are the objects which the actor has witnessed leaving
2239
         *   the actor's present location 
2240
         */
2241
        scope_ = scope_.appendUnique(targetActor.getFollowables());
2242
    }
2243
;
2244
2245
DefineTAction(Attack)
2246
;
2247
2248
DefineTIAction(AttackWith)
2249
    /* 
2250
     *   for the indirect object, limit 'all' and defaults to the items in
2251
     *   inventory 
2252
     */
2253
    getAllIobj(actor, scopeList)
2254
    {
2255
        return scopeList.subset({x: x.isIn(actor)});
2256
    }
2257
;
2258
2259
DefineTAction(Throw)
2260
;
2261
2262
DefineTAction(ThrowDir)
2263
    /* get the direction of the throwing (as a Direction object) */
2264
    getDirection() { return dirMatch.dir; }
2265
;
2266
2267
DefineTIAction(ThrowAt)
2268
;
2269
2270
DefineTIAction(ThrowTo)
2271
;
2272
2273
DefineTAction(Dig)
2274
;
2275
2276
DefineTIAction(DigWith)
2277
    /* limit 'all' for the indirect object to items in inventory */
2278
    getAllIobj(actor, scopeList)
2279
    {
2280
        return scopeList.subset({x: x.isIn(actor)});
2281
    }
2282
;
2283
2284
DefineIAction(Jump)
2285
    preCond = [actorStanding]
2286
    execAction()
2287
    {
2288
        /* show the default report for jumping in place */
2289
        mainReport(&okayJumpMsg);
2290
    }
2291
;
2292
2293
DefineTAction(JumpOver)
2294
;
2295
2296
DefineTAction(JumpOff)
2297
;
2298
2299
DefineIAction(JumpOffI)
2300
    execAction()
2301
    {
2302
        mainReport(&cannotJumpOffHereMsg);
2303
    }
2304
;
2305
2306
DefineTAction(Push)
2307
;
2308
2309
DefineTAction(Pull)
2310
;
2311
2312
DefineTAction(Move)
2313
;
2314
2315
DefineTIAction(MoveWith)
2316
    /* limit 'all' for the indirect object to items in inventory */
2317
    getAllIobj(actor, scopeList)
2318
    {
2319
        return scopeList.subset({x: x.isIn(actor)});
2320
    }
2321
;
2322
2323
DefineTIAction(MoveTo)
2324
;
2325
2326
DefineTAction(Turn)
2327
;
2328
2329
DefineTIAction(TurnWith)
2330
    /* limit 'all' for the indirect object to items in inventory */
2331
    getAllIobj(actor, scopeList)
2332
    {
2333
        return scopeList.subset({x: x.isIn(actor)});
2334
    }
2335
;
2336
2337
DefineLiteralTAction(TurnTo, IndirectObject)
2338
;
2339
2340
DefineTAction(Set)
2341
;
2342
2343
DefineLiteralTAction(SetTo, IndirectObject)
2344
;
2345
2346
DefineTAction(TypeOn)
2347
;
2348
2349
DefineLiteralTAction(TypeLiteralOn, DirectObject)
2350
;
2351
2352
DefineLiteralTAction(EnterOn, DirectObject)
2353
;
2354
2355
DefineTAction(Consult)
2356
;
2357
2358
DefineTopicTAction(ConsultAbout, IndirectObject)
2359
    getDefaultDobj(np, resolver)
2360
    {
2361
        local actor;
2362
        local obj;
2363
        
2364
        /* 
2365
         *   if the actor has consulted something before, and that object
2366
         *   is still visible, use it as the default this consultation 
2367
         */
2368
        actor = resolver.getTargetActor();
2369
        obj = actor.lastConsulted;
2370
        if (obj != nil && actor.canSee(obj))
2371
            return [new ResolveInfo(obj, DefaultObject)];
2372
        else
2373
            return inherited(np, resolver);
2374
    }
2375
2376
    /*
2377
     *   Filter the topic phrase resolution.  If we know our direct object,
2378
     *   and it's a Consultable, refer the resolution to the Consultable.  
2379
     */
2380
    filterTopic(lst, np, resolver)
2381
    {
2382
        local dobj;
2383
        
2384
        /* check the direct object */
2385
        if (dobjList_ != nil
2386
            && dobjList_.length() == 1
2387
            && (dobj = dobjList_[1].obj_).ofKind(Consultable))
2388
        {
2389
            /* 
2390
             *   we have a Consultable direct object - let it handle the
2391
             *   topic phrase resolution 
2392
             */
2393
            return dobj.resolveConsultTopic(lst, topicMatch, resolver);
2394
        }
2395
        else
2396
        {
2397
            /* otherwise, use the default handling */
2398
            return inherited(lst, np, resolver);
2399
        }
2400
    }
2401
;
2402
2403
DefineTAction(Switch)
2404
;
2405
2406
DefineTAction(Flip)
2407
;
2408
2409
DefineTAction(TurnOn)
2410
;
2411
2412
DefineTAction(TurnOff)
2413
;
2414
2415
DefineTAction(Light)
2416
;
2417
2418
DefineTAction(Burn)
2419
;
2420
2421
DefineTIAction(BurnWith)
2422
    /* limit 'all' for the indirect object to items in inventory */
2423
    getAllIobj(actor, scopeList)
2424
    {
2425
        return scopeList.subset({x: x.isIn(actor)});
2426
    }
2427
2428
    /* resolve the direct object first */
2429
    resolveFirst = DirectObject
2430
;
2431
2432
DefineTAction(Extinguish)
2433
;
2434
2435
DefineTIAction(AttachTo)
2436
;
2437
2438
DefineTIAction(DetachFrom)
2439
;
2440
2441
DefineTAction(Detach)
2442
;
2443
2444
DefineTAction(Break)
2445
;
2446
2447
DefineTAction(Cut)
2448
;
2449
2450
DefineTIAction(CutWith)
2451
;
2452
2453
DefineTAction(Climb)
2454
;
2455
2456
DefineTAction(ClimbUp)
2457
;
2458
2459
DefineTAction(ClimbDown)
2460
;
2461
2462
DefineTAction(Open)
2463
;
2464
2465
DefineTAction(Close)
2466
;
2467
2468
DefineTAction(Lock)
2469
;
2470
2471
DefineTAction(Unlock)
2472
;
2473
2474
DefineTIAction(LockWith)
2475
    /* 
2476
     *   Resolve the direct object (the lock) first, so that we know what
2477
     *   we're trying to unlock when we're verifying the key.  This allows
2478
     *   us to (optionally) boost the likelihood of a known good key for
2479
     *   disambiguation. 
2480
     */
2481
    resolveFirst = DirectObject
2482
2483
    /* limit 'all' for the indirect object to items in inventory */
2484
    getAllIobj(actor, scopeList)
2485
    {
2486
        return scopeList.subset({x: x.isIn(actor)});
2487
    }
2488
;
2489
2490
DefineTIAction(UnlockWith)
2491
    /* resolve the direct object first, for the same reason as in LockWith */
2492
    resolveFirst = DirectObject
2493
2494
    /* limit 'all' for the indirect object to items in inventory */
2495
    getAllIobj(actor, scopeList)
2496
    {
2497
        return scopeList.subset({x: x.isIn(actor)});
2498
    }
2499
;
2500
2501
DefineTAction(Eat)
2502
;
2503
2504
DefineTAction(Drink)
2505
;
2506
2507
DefineTAction(Pour)
2508
;
2509
2510
DefineTIAction(PourInto)
2511
;
2512
2513
DefineTIAction(PourOnto)
2514
;
2515
2516
DefineTAction(Clean)
2517
;
2518
2519
DefineTIAction(CleanWith)
2520
    /* limit 'all' for the indirect object to items in inventory */
2521
    getAllIobj(actor, scopeList)
2522
    {
2523
        return scopeList.subset({x: x.isIn(actor)});
2524
    }
2525
;
2526
2527
DefineIAction(Sit)
2528
    execAction()
2529
    {
2530
        /* 
2531
         *   if the actor is already sitting, just say so; otherwise, ask
2532
         *   what they want to sit on 
2533
         */
2534
        if (gActor.posture == sitting)
2535
            reportFailure(&alreadySittingMsg);
2536
        else
2537
            askForDobj(SitOn);
2538
    }
2539
;
2540
2541
DefineTAction(SitOn)
2542
;
2543
2544
DefineIAction(Lie)
2545
    execAction()
2546
    {
2547
        /* 
2548
         *   if the actor is already lying down, just say so; otherwise,
2549
         *   ask what they want to lie on 
2550
         */
2551
        if (gActor.posture == lying)
2552
            reportFailure(&alreadyLyingMsg);
2553
        else
2554
            askForDobj(LieOn);
2555
    }
2556
;
2557
2558
DefineTAction(LieOn)
2559
;
2560
2561
DefineTAction(StandOn)
2562
;
2563
2564
DefineIAction(Stand)
2565
    execAction()
2566
    {
2567
        /* let the actor handle it */
2568
        gActor.standUp();
2569
    }
2570
;
2571
2572
DefineTAction(Board)
2573
;
2574
2575
DefineTAction(GetOutOf)
2576
    getAllDobj(actor, scopeList)
2577
    {
2578
        /* 'all' for 'get out of' is the actor's immediate container */
2579
        return scopeList.subset({x: actor.isDirectlyIn(x)});
2580
    }
2581
;
2582
2583
DefineTAction(GetOffOf)
2584
    getAllDobj(actor, scopeList)
2585
    {
2586
        /* 'all' for 'get off of' is the actor's immediate container */
2587
        return scopeList.subset({x: actor.isDirectlyIn(x)});
2588
    }
2589
;
2590
2591
DefineIAction(GetOut)
2592
    execAction()
2593
    {
2594
        /* let the actor handle it */
2595
        gActor.disembark();
2596
    }
2597
;
2598
2599
DefineTAction(Fasten)
2600
;
2601
2602
DefineTIAction(FastenTo)
2603
;
2604
2605
DefineTAction(Unfasten)
2606
;
2607
2608
DefineTIAction(UnfastenFrom)
2609
;
2610
2611
DefineTAction(PlugIn)
2612
;
2613
2614
DefineTIAction(PlugInto)
2615
;
2616
2617
DefineTAction(Unplug)
2618
;
2619
2620
DefineTIAction(UnplugFrom)
2621
;
2622
2623
DefineTAction(Screw)
2624
;
2625
2626
DefineTIAction(ScrewWith)
2627
    /* limit 'all' for the indirect object to items in inventory */
2628
    getAllIobj(actor, scopeList)
2629
    {
2630
        return scopeList.subset({x: x.isIn(actor)});
2631
    }
2632
;
2633
2634
DefineTAction(Unscrew)
2635
;
2636
2637
DefineTIAction(UnscrewWith)
2638
    /* limit 'all' for the indirect object to items in inventory */
2639
    getAllIobj(actor, scopeList)
2640
    {
2641
        return scopeList.subset({x: x.isIn(actor)});
2642
    }
2643
;
2644
2645
/* ------------------------------------------------------------------------ */
2646
/*
2647
 *   Travel Action - this is the base class for verbs that attempt to move
2648
 *   an actor to a new location via one of the directional connections
2649
 *   from the current location.
2650
 *   
2651
 *   Each grammar rule for this action must set the 'dirMatch' property to
2652
 *   a DirectionProd match object that gives the direction.  
2653
 */
2654
DefineIAction(Travel)
2655
    execAction()
2656
    {
2657
        local conn;
2658
        
2659
        /* 
2660
         *   Perform the travel via the connector, if we have one.  If
2661
         *   there's no connector defined for this direction, show a
2662
         *   default "you can't go that way" message. 
2663
         */
2664
        if ((conn = getConnector()) != nil)
2665
        {
2666
            /* 
2667
             *   we have a connector - use the pseudo-action TravelVia with
2668
             *   the connector to carry out the travel 
2669
             */
2670
            replaceAction(TravelVia, conn);
2671
        }
2672
        else
2673
        {
2674
            /* no connector - show a default "can't go that way" error */
2675
            mainReport(&cannotGoThatWayMsg);
2676
        }
2677
    }
2678
2679
    /* get the direction object for the travel */
2680
    getDirection() { return dirMatch != nil ? dirMatch.dir : nil; }
2681
2682
    /*
2683
     *   Get my travel connector.  My connector is given by the travel
2684
     *   link property for this action as defined in the actor's current
2685
     *   location. 
2686
     */
2687
    getConnector()
2688
    {
2689
        /* ask the location for the connector in my direction */
2690
        return gActor.location == nil
2691
            ? nil
2692
            : gActor.location.getTravelConnector(getDirection(), gActor);
2693
    }
2694
2695
    /*
2696
     *   The grammar rules for the individual directions will usually just
2697
     *   create a base TravelAction object, rather than one of the
2698
     *   direction-specific subclasses (NorthAction, etc).  For
2699
     *   convenience in testing the action, though, treat ourself as
2700
     *   matching the subclass with the same direction.  
2701
     */
2702
    actionOfKind(cls)
2703
    {
2704
        /* 
2705
         *   If they're asking about a specific-direction TravelAction
2706
         *   subclass, then we match it if our own direction matches that
2707
         *   of the given subclass, and we fail to match if our direction
2708
         *   doesn't match the given direction.  
2709
         */
2710
        if (cls.ofKind(TravelAction) && cls.getDirection() != nil)
2711
        {
2712
            /* we match if and only if the direction matches */
2713
            return (getDirection() == cls.getDirection());
2714
        }
2715
2716
        /* otherwise, inherit the default handling */
2717
        return inherited(cls);
2718
    }
2719
;
2720
2721
/* for a vague command such as GO, which doesn't say where to go */
2722
DefineIAction(VagueTravel)
2723
    execAction()
2724
    {
2725
        /* simply ask for a direction */
2726
        reportFailure(&whereToGoMsg);
2727
    }
2728
;
2729
2730
/*
2731
 *   This class makes it convenient to synthesize a TravelAction given a
2732
 *   Direction object.  To create a travel action for a direction, use
2733
 *   
2734
 *.     new TravelDirAction(direction)
2735
 *   
2736
 *   where 'direction' is the direction object (northDirection, etc) for
2737
 *   the desired direction of travel.  Note that if you want to use the
2738
 *   resulting object in replaceAction() or one of the similar macros,
2739
 *   you'll need to go directly to the underlying function rather than
2740
 *   using the standard macro, since the macros expect a literal action
2741
 *   name rather than an object.  For example:
2742
 *   
2743
 *.     _replaceAction(gActor, new TravelDirAction(getDirection));
2744
 */
2745
DefineAction(TravelDir, TravelAction)
2746
    construct(dir)
2747
    {
2748
        /* remember my direction */
2749
        dir_ = dir;
2750
    }
2751
    
2752
    /* get my direction */
2753
    getDirection() { return dir_; }
2754
    
2755
    /* my direction, normally specified during construction */
2756
    dir_ = nil
2757
;
2758
2759
/*
2760
 *   To make it more convenient to use directional travel actions as
2761
 *   synthesized commands, define a set of action classes for the specific
2762
 *   directions.  
2763
 */
2764
DefineAction(North, TravelAction)
2765
    getDirection = northDirection
2766
;
2767
DefineAction(South, TravelAction)
2768
    getDirection = southDirection
2769
;
2770
DefineAction(East, TravelAction)
2771
    getDirection = eastDirection
2772
;
2773
DefineAction(West, TravelAction)
2774
    getDirection = westDirection
2775
;
2776
DefineAction(Northeast, TravelAction)
2777
    getDirection = northeastDirection
2778
;
2779
DefineAction(Northwest, TravelAction)
2780
    getDirection = northwestDirection
2781
;
2782
DefineAction(Southeast, TravelAction)
2783
    getDirection = southeastDirection
2784
;
2785
DefineAction(Southwest, TravelAction)
2786
    getDirection = southwestDirection
2787
;
2788
DefineAction(In, TravelAction)
2789
    getDirection = inDirection
2790
;
2791
DefineAction(Out, TravelAction)
2792
    getDirection = outDirection
2793
;
2794
DefineAction(Up, TravelAction)
2795
    getDirection = upDirection
2796
;
2797
DefineAction(Down, TravelAction)
2798
    getDirection = downDirection
2799
;
2800
DefineAction(Fore, TravelAction)
2801
    getDirection = foreDirection
2802
;
2803
DefineAction(Aft, TravelAction)
2804
    getDirection = aftDirection
2805
;
2806
DefineAction(Port, TravelAction)
2807
    getDirection = portDirection
2808
;
2809
DefineAction(Starboard, TravelAction)
2810
    getDirection = starboardDirection
2811
;
2812
2813
/*
2814
 *   Non-directional travel actions 
2815
 */
2816
2817
DefineTAction(GoThrough)
2818
;
2819
2820
DefineTAction(Enter)
2821
;
2822
2823
/*
2824
 *   An internal action for traveling via a connector.  This isn't a real
2825
 *   action, and shouldn't have a grammar defined for it.  The purpose of
2826
 *   this action is to allow real actions that cause travel via a
2827
 *   connector to be implemented by mapping to this internal action, which
2828
 *   we implement on the base travel connector class.  
2829
 */
2830
DefineTAction(TravelVia)
2831
    /* 
2832
     *   The direct object of this synthetic action isn't necessarily an
2833
     *   ordinary simulation object: it could be a TravelConnector instead.
2834
     *   Since callers asking for a direct object almost always expect a
2835
     *   simulation object, returning a non-simulation object here can be
2836
     *   problematic.  To avoid this, we return an empty object list by
2837
     *   default - this ensures that no one who asks for the direct object
2838
     *   of the verb will get back a non-simulation travel connector.  
2839
     */
2840
    getCurrentObjects = []
2841
;
2842
2843
/* "go back" */
2844
DefineIAction(GoBack)
2845
    execAction()
2846
    {
2847
        /* ask the actor to handle it */
2848
        gActor.reverseLastTravel();
2849
    }
2850
;
2851
2852
/* ------------------------------------------------------------------------ */
2853
/*
2854
 *   Combined pushing-and-traveling action ("push crate north", "drag sled
2855
 *   into cave").  All of these are based on a base action class, which
2856
 *   defines the methods invoked on the object being pushed; the
2857
 *   subclasses provide a definition of the connector that determines
2858
 *   where the travel takes us.  
2859
 */
2860
DefineTAction(PushTravel)
2861
    /*
2862
     *   Carry out the nested travel action for the special combination
2863
     *   push-traveler.  This should carry out the same action we would
2864
     *   have performed for the underlying basic travel.
2865
     *   
2866
     *   This method is invoked by the TravelPushable to carry out a
2867
     *   push-travel action.  The TravelPushable object will first set up
2868
     *   a PushTraveler as the actor's global traveler, and it will then
2869
     *   invoke this method to carry out the actual travel with that
2870
     *   special traveler in effect.  Our job is to provide the mapping to
2871
     *   the correct underlying simple travel action; since we'll be
2872
     *   moving the PushTraveler object, we can move it using the ordinary
2873
     *   non-push travel action as though it were any other traveler.
2874
     *   
2875
     *   This method is abstract - each subclass must define it
2876
     *   appropriately.  
2877
     */
2878
    // performTravel() { }
2879
;
2880
2881
/*
2882
 *   For directional push-and-travel commands, we define a common base
2883
 *   class that does the work to find the connector based on the room's
2884
 *   directional connector.
2885
 *   
2886
 *   Subclasses for grammar rules must define the 'dirMatch' property to
2887
 *   be a DirectionProd object for the associated direction.  
2888
 */
2889
DefineAction(PushTravelDir, PushTravelAction)
2890
    /*
2891
     *   Get the direction we're going.  By default, we return the
2892
     *   direction associated with the dirMatch match object from our
2893
     *   grammar match.  
2894
     */
2895
    getDirection() { return dirMatch.dir; }
2896
2897
    /* carry out the nested travel action for a PushTravel */
2898
    performTravel()
2899
    {
2900
        local conn;
2901
        
2902
        /* ask the actor's location for the connector in our direction */
2903
        conn = gActor.location.getTravelConnector(getDirection(), gActor);
2904
2905
        /* perform a nested TravelVia on the connector */
2906
        nestedAction(TravelVia, conn);
2907
    }
2908
;
2909
2910
/*
2911
 *   To make it easy to synthesize actions for pushing objects, define
2912
 *   individual subclasses for the various directions.
2913
 */
2914
DefineAction(PushNorth, PushTravelDirAction)
2915
    getDirection = northDirection
2916
;
2917
2918
DefineAction(PushSouth, PushTravelDirAction)
2919
    getDirection = southDirection
2920
;
2921
2922
DefineAction(PushEast, PushTravelDirAction)
2923
    getDirection = eastDirection
2924
;
2925
2926
DefineAction(PushWest, PushTravelDirAction)
2927
    getDirection = westDirection
2928
;
2929
2930
DefineAction(PushNorthwest, PushTravelDirAction)
2931
    getDirection = northwestDirection
2932
;
2933
2934
DefineAction(PushNortheast, PushTravelDirAction)
2935
    getDirection = northeastDirection
2936
;
2937
2938
DefineAction(PushSouthwest, PushTravelDirAction)
2939
    getDirection = southwestDirection
2940
;
2941
2942
DefineAction(PushSoutheast, PushTravelDirAction)
2943
    getDirection = southeastDirection
2944
;
2945
2946
DefineAction(PushUp, PushTravelDirAction)
2947
    getDirection = upDirection
2948
;
2949
2950
DefineAction(PushDown, PushTravelDirAction)
2951
    getDirection = downDirection
2952
;
2953
2954
DefineAction(PushIn, PushTravelDirAction)
2955
    getDirection = inDirection
2956
;
2957
2958
DefineAction(PushOut, PushTravelDirAction)
2959
    getDirection = outDirection
2960
;
2961
2962
DefineAction(PushFore, PushTravelDirAction)
2963
    getDirection = foreDirection
2964
;
2965
2966
DefineAction(PushAft, PushTravelDirAction)
2967
    getDirection = aftDirection
2968
;
2969
2970
DefineAction(PushPort, PushTravelDirAction)
2971
    getDirection = portDirection
2972
;
2973
2974
DefineAction(PushStarboard, PushTravelDirAction)
2975
    getDirection = starboardDirection
2976
;
2977
2978
/*
2979
 *   Base class for two-object push-travel commands, such as "push boulder
2980
 *   out of cave" or "drag sled up hill".  For all of these, the connector
2981
 *   is given by the indirect object.
2982
 */
2983
DefineAction(PushTravelViaIobj, TIAction, PushTravelAction)
2984
    /*
2985
     *   Verify the indirect object of the push-travel action.  We'll
2986
     *   remap this to given corresponding simple travel action, and call
2987
     *   that action's verifier.  
2988
     */
2989
    verifyPushTravelIobj(obj, action)
2990
    {
2991
        /* handle this by remapping it to the underlying simple action */
2992
        remapVerify(IndirectObject, gVerifyResults, [action, obj]);
2993
    }
2994
;
2995
2996
DefineTIActionSub(PushTravelThrough, PushTravelViaIobjAction)
2997
    /* 
2998
     *   Carry out the underlying simple travel action.  This simply
2999
     *   performs a GoThrough on my indirect object, as though we had
3000
     *   typed simply GO THROUGH iobj.  The PushTraveler will already be
3001
     *   set up as the actor's special traveler, so the ordinary GO
3002
     *   THROUGH command will move the special PushTraveler object as
3003
     *   though it were the original actor.  
3004
     */
3005
    performTravel() { nestedAction(GoThrough, getIobj()); }
3006
;
3007
3008
DefineTIActionSub(PushTravelEnter, PushTravelViaIobjAction)
3009
    /* carry out the underlying simple travel as an ENTER action */
3010
    performTravel() { nestedAction(Enter, getIobj()); }
3011
;
3012
3013
DefineTIActionSub(PushTravelGetOutOf, PushTravelViaIobjAction)
3014
    /* carry out the underlying simple travel as a GET OUT OF action */
3015
    performTravel() { nestedAction(GetOutOf, getIobj()); }
3016
;
3017
3018
DefineTIActionSub(PushTravelClimbUp, PushTravelViaIobjAction)
3019
    /* carry out the underlying simple travel as a CLIMB UP action */
3020
    performTravel() { nestedAction(ClimbUp, getIobj()); }
3021
;
3022
3023
DefineTIActionSub(PushTravelClimbDown, PushTravelViaIobjAction)
3024
    /* carry out the underlying simple travel as an CLIMB DOWN action */
3025
    performTravel() { nestedAction(ClimbDown, getIobj()); }
3026
;
3027
3028
/*
3029
 *   The "exits" verb.  This verb explicitly shows all of the exits from
3030
 *   the current location. 
3031
 */
3032
DefineIAction(Exits)
3033
    execAction()
3034
    {
3035
        /* 
3036
         *   if we have an exit lister object, invoke it; otherwise,
3037
         *   explain that this command isn't supported in this game 
3038
         */
3039
        if (gExitLister != nil)
3040
            gExitLister.showExitsCommand();
3041
        else
3042
            gLibMessages.commandNotPresent;
3043
    }
3044
;
3045
3046
/* in case the exits module isn't included */
3047
property showExitsCommand, exitsOnOffCommand;
3048
3049
/*
3050
 *   Change the exit display mode.  The grammar must set one of the mode
3051
 *   token properties to a non-nil value, according to which mode the
3052
 *   player selected: on_ for turning on statusline and description lists;
3053
 *   stat_ for turning on only the statusline list; look_ for turning on
3054
 *   only the room description list; and off_ for turning off everything.  
3055
 */
3056
DefineSystemAction(ExitsMode)
3057
    execSystemAction()
3058
    {
3059
        local stat, look;
3060
3061
        /* 
3062
         *   If it's EXITS ON, turn on both statusline and room description
3063
         *   lists.  If it's EXITS LOOK or EXITS STATUS, just turn on one
3064
         *   or the other.  Otherwise, turn both off. 
3065
         */
3066
        stat = (stat_ != nil || on_ != nil);
3067
        look = (look_ != nil || on_ != nil);
3068
3069
        /* update the exit display */
3070
        if (gExitLister != nil)
3071
            gExitLister.exitsOnOffCommand(stat, look);
3072
        else
3073
            gLibMessages.commandNotPresent;
3074
    }
3075
;
3076
3077
/*
3078
 *   Dummy OOPS action for times when OOPS isn't in context.  We'll simply
3079
 *   explain how OOPS works, and that you can't use it right now.  
3080
 */
3081
DefineLiteralAction(Oops)
3082
    execAction()
3083
    {
3084
        /* simply explain how this command works */
3085
        gLibMessages.oopsOutOfContext;
3086
    }
3087
3088
    /* this is a meta-command, so don't consume any time */
3089
    actionTime = 0
3090
;
3091
3092
/* intransitive form of "oops" */
3093
DefineIAction(OopsI)
3094
    doActionMain()
3095
    {
3096
        /* as with OOPS with a literal, simply explain the problem */
3097
        gLibMessages.oopsOutOfContext;
3098
    }
3099
3100
    /* this is a meta-command, so don't consume any time */
3101
    actionTime = 0
3102
;
3103
3104
property disableHints, showHints;
3105
3106
/* hint system - disable hints for this session */
3107
DefineSystemAction(HintsOff)
3108
    execSystemAction()
3109
    {
3110
        if (gHintManager != nil)
3111
            gHintManager.disableHints();
3112
        else
3113
            mainReport(gLibMessages.hintsNotPresent);
3114
    }
3115
;
3116
3117
/* invoke hint system */
3118
DefineSystemAction(Hint)
3119
    execSystemAction()
3120
    {
3121
        if (gHintManager != nil)
3122
            gHintManager.showHints();
3123
        else
3124
            mainReport(gLibMessages.hintsNotPresent);
3125
    }
3126
;
3127
3128
/* ------------------------------------------------------------------------ */
3129
/*
3130
 *   Parser debugging verbs 
3131
 */
3132
3133
#ifdef PARSER_DEBUG
3134
3135
DefineIAction(ParseDebug)
3136
    execAction()
3137
    {
3138
        local newMode;
3139
3140
        /* 
3141
         *   get the mode - if the mode is explicitly stated in the
3142
         *   command, use the stated new mode, otherwise invert the current
3143
         *   mode 
3144
         */
3145
        newMode = (onOrOff_ == 'on'
3146
                   ? true
3147
                   : onOrOff_ == 'off'
3148
                   ? nil
3149
                   : !libGlobal.parserDebugMode);
3150
3151
        /* set the new mode */
3152
        libGlobal.parserDebugMode = newMode;
3153
3154
        /* mention the change */
3155
        "Parser debugging is now
3156
        <<libGlobal.parserDebugMode ? 'on' : 'off'>>.\n";
3157
    }
3158
;
3159
3160
grammar predicate(ParseDebug):
3161
    'parse-debug' 'on'->onOrOff_
3162
    | 'parse-debug' 'off'->onOrOff_
3163
    | 'parse-debug'
3164
    : ParseDebugAction
3165
;
3166
3167
#endif
3168