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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2002, 2006 by Michael J. Roberts
5
 *   
6
 *   Based on exitslister.t, copyright 2002 by Steve Breslin and
7
 *   incorporated by permission.  
8
 *   
9
 *   TADS 3 Library - Exits Lister
10
 *   
11
 *   This module provides an automatic exit lister that shows the apparent
12
 *   exits from the player character's location.  The automatic exit lister
13
 *   can optionally provide these main features:
14
 *   
15
 *   - An "exits" verb lets the player explicitly show the list of apparent
16
 *   exits, along with the name of the room to which each exit connects.
17
 *   
18
 *   - Exits can be shown automatically as part of the room description.
19
 *   This extra information can be controlled by the player through the
20
 *   "exits on" and "exits off" command.
21
 *   
22
 *   - Exits can be shown automatically when an actor tries to go in a
23
 *   direction where no exit exists, as a helpful reminder of which
24
 *   directions are valid.  
25
 */
26
27
/* include the library header */
28
#include "adv3.h"
29
30
31
/* ------------------------------------------------------------------------ */
32
/*
33
 *   The main exits lister.
34
 */
35
exitLister: PreinitObject
36
    /* preinitialization */
37
    execute()
38
    {
39
        /* install myself as the global exit lister object */
40
        gExitLister = self;
41
    }
42
    
43
    /*
44
     *   Flag: use "verbose" listing style for exit lists in room
45
     *   descriptions.  When this is set to true, we'll show a
46
     *   sentence-style list of exits ("Obvious exits lead east to the
47
     *   living room, south, and up.").  When this is set to nil, we'll use
48
     *   a terse style, enclosing the message in the default system
49
     *   message's brackets ("[Obvious exits: East, West]").
50
     *   
51
     *   Verbose-style room descriptions tend to fit well with a room
52
     *   description's prose, but at the expense of looking redundant with
53
     *   the exit list that's usually built into each room's custom
54
     *   descriptive text to begin with.  Some authors prefer the terse
55
     *   style precisely because it doesn't look like more prose
56
     *   description, but looks like a separate bit of information being
57
     *   offered.
58
     *   
59
     *   This is an author-configured setting; the library does not provide
60
     *   a command to let the player control this setting.  
61
     */
62
    roomDescVerbose = nil
63
64
    /* 
65
     *   Flag: show automatic exit listings on attempts to move in
66
     *   directions that don't allow travel.  Enable this by default,
67
     *   since most players appreciate having the exit list called out
68
     *   separately from the room description (where any mention of exits
69
     *   might be buried in lots of other text) in place of an unspecific
70
     *   "you can't go that way".  
71
     *   
72
     *   This is an author-configured setting; the library does not provide
73
     *   a command to let the player control this setting.  
74
     */
75
    enableReminder = true
76
77
    /*
78
     *   Flag: enable the automatic exit reminder even when the room
79
     *   description exit listing is enabled.  When this is nil, we will
80
     *   NOT show a reminder with "can't go that way" messages when the
81
     *   room description exit list is enabled - this is the default,
82
     *   because it can be a little much to have the list of exits shown so
83
     *   frequently.  Some authors might prefer to show the reminder
84
     *   unconditionally, though, so this option is offered.  
85
     *   
86
     *   This is an author-configured setting; the library does not provide
87
     *   a command to let the player control this setting.  
88
     */
89
    enableReminderAlways = nil
90
91
    /*
92
     *   Flag: use hyperlinks in the directions mentioned in room
93
     *   description exit lists, so that players can click on the direction
94
     *   name in the listing to enter the direction command. 
95
     */
96
    enableHyperlinks = true
97
98
    /* flag: we've explained how the exits on/off command works */
99
    exitsOnOffExplained = nil
100
101
    /*
102
     *   Determine if the "reminder" is enabled.  The reminder is the list
103
     *   of exits we show along with a "can't go that way" message, to
104
     *   reminder the player of the valid exits when an invalid one is
105
     *   attempted.  
106
     */
107
    isReminderEnabled()
108
    {
109
        /*   
110
         *   The reminder is enabled if enableReminderAlways is true, OR if
111
         *   enableReminder is true AND exitsMode.inRoomDesc is nil.  
112
         */
113
        return (enableReminderAlways
114
                || (enableReminder && !exitsMode.inRoomDesc));
115
    }
116
117
    /*
118
     *   Get the exit lister we use for room descriptions. 
119
     */
120
    getRoomDescLister()
121
    {
122
        /* use the verbose or terse lister, according to the configuration */
123
        return roomDescVerbose
124
            ? lookAroundExitLister
125
            : lookAroundTerseExitLister;
126
    }
127
    
128
    /* perform the "exits" command to show exits on explicit request */
129
    showExitsCommand()
130
    {
131
        /* show exits for the current actor */
132
        showExits(gActor);
133
134
        /* 
135
         *   if we haven't explained how to turn exit listing on and off,
136
         *   do so now 
137
         */
138
        if (!exitsOnOffExplained)
139
        {
140
            gLibMessages.explainExitsOnOff;
141
            exitsOnOffExplained = true;
142
        }
143
    }
144
145
    /* 
146
     *   Perform an EXITS ON/OFF/STATUS/LOOK command.  'stat' indicates
147
     *   whether we're turning on (true) or off (nil) the statusline exit
148
     *   listing; 'look' indicates whether we're turning the room
149
     *   description listing on or off. 
150
     */
151
    exitsOnOffCommand(stat, look)
152
    {
153
        /* set the new status */
154
        exitsMode.inStatusLine = stat;
155
        exitsMode.inRoomDesc = look;
156
157
        /* confirm the new status */
158
        gLibMessages.exitsOnOffOkay(stat, look);
159
160
        /* 
161
         *   If we haven't already explained how the EXITS ON/OFF command
162
         *   works, don't bother explaining it now, since they obviously
163
         *   know how it works if they've actually used it.  
164
         */
165
        exitsOnOffExplained = true;
166
    }
167
    
168
    /* show the list of exits from an actor's current location */
169
    showExits(actor)
170
    {
171
        /* show exits from the actor's location */
172
        showExitsFrom(actor, actor.location);
173
    }
174
175
    /* show an exit list display in the status line, if desired */
176
    showStatuslineExits()
177
    {
178
        /* if statusline exit displays are enabled, show the exit list */
179
        if (exitsMode.inStatusLine)
180
            showExitsWithLister(gPlayerChar, gPlayerChar.location,
181
                                statuslineExitLister,
182
                                gPlayerChar.location
183
                                .wouldBeLitFor(gPlayerChar));
184
    }
185
186
    /* 
187
     *   Calculate the contribution of the exits list to the height of the
188
     *   status line, in lines of text.  If we're not configured to display
189
     *   the exits list in the status line, then the contribution is zero;
190
     *   otherwise, we'll estimate how much space we need to display the
191
     *   exit list.  
192
     */
193
    getStatuslineExitsHeight()
194
    {
195
        /* 
196
         *   if we're enabled, our standard display takes up one line; if
197
         *   we're disabled, we don't contribute anything to the status
198
         *   line's vertical extent 
199
         */
200
        if (exitsMode.inStatusLine)
201
            return 1;
202
        else
203
            return 0;
204
    }
205
206
    /* show exits as part of a room description */
207
    lookAroundShowExits(actor, loc, illum)
208
    {
209
        /* if room exit displays are enabled, show the exits */
210
        if (exitsMode.inRoomDesc)
211
            showExitsWithLister(actor, loc, getRoomDescLister, illum);
212
    }
213
214
    /* show exits as part of a "cannot go that way" error */
215
    cannotGoShowExits(actor, loc)
216
    {
217
        /* if we want to show the reminder, show it */
218
        if (isReminderEnabled())
219
            showExitsWithLister(actor, loc, explicitExitLister,
220
                                loc.wouldBeLitFor(actor));
221
    }
222
223
    /* show the list of exits from a given location for a given actor */
224
    showExitsFrom(actor, loc)
225
    {
226
        /* show exits with our standard lister */
227
        showExitsWithLister(actor, loc, explicitExitLister,
228
                            loc.wouldBeLitFor(actor));
229
    }
230
231
    /* 
232
     *   Show the list of exits using a specific lister.
233
     *   
234
     *   'actor' is the actor for whom the display is being generated.
235
     *   'loc' is the location whose exit list is to be shown; this need
236
     *   not be the same as the actor's current location.  'lister' is the
237
     *   Lister object that will show the list of DestInfo objects that we
238
     *   create to represent the exit list.
239
     *   
240
     *   'locIsLit' indicates whether or not the ambient illumination, for
241
     *   the actor's visual senses, is sufficient that the actor would be
242
     *   able to see if the actor were in the new location.  We take this
243
     *   as a parameter so that we don't have to re-compute the
244
     *   information if the caller has already computed it for other
245
     *   reasons (such as showing a room description).  If the caller
246
     *   hasn't otherwise computed the value, it can be easily computed as
247
     *   loc.wouldBeLitFor(actor).  
248
     */
249
    showExitsWithLister(actor, loc, lister, locIsLit)
250
    {
251
        local destList;
252
        local showDest;
253
        local options;
254
255
        /* 
256
         *   Ask the lister if it shows the destination names.  We need to
257
         *   know because we want to consolidate exits that go to the same
258
         *   place if and only if we're going to show the destination in
259
         *   the listing; if we're not showing the destination, there's no
260
         *   reason to consolidate. 
261
         */
262
        showDest = lister.listerShowsDest;
263
264
        /* we have no option flags for the lister yet */
265
        options = 0;
266
267
        /* run through all of the directions used in the game */
268
        destList = new Vector(Direction.allDirections.length());
269
        foreach (local dir in Direction.allDirections)
270
        {
271
            local conn;
272
            
273
            /* 
274
             *   If the actor's location has a connector in this
275
             *   direction, and the connector is apparent, add it to the
276
             *   list.
277
             *   
278
             *   If the actor is in the dark, we can only see the
279
             *   connector if the connector is visible in the dark.  If
280
             *   the actor isn't in the dark, we can show all of the
281
             *   connectors.  
282
             */
283
            if ((conn = loc.getTravelConnector(dir, actor)) != nil
284
                && conn.isConnectorApparent(loc, actor)
285
                && conn.isConnectorListed
286
                && (locIsLit || conn.isConnectorVisibleInDark(loc, actor)))
287
            {
288
                local dest;
289
                local destName = nil;
290
                local destIsBack;
291
292
                /* 
293
                 *   We have an apparent connection in this direction, so
294
                 *   add it to our list.  First, check to see if we know
295
                 *   the destination. 
296
                 */
297
                dest = conn.getApparentDestination(loc, actor);
298
299
                /* note if this is the "back to" connector for the actor */
300
                destIsBack = (conn == actor.lastTravelBack);
301
302
                /* 
303
                 *   If we know the destination, and they want to include
304
                 *   destination names where possible, get the name.  If
305
                 *   there's a name to show, include the name.  
306
                 */
307
                if (dest != nil
308
                    && showDest
309
                    && (destName = dest.getDestName(actor, loc)) != nil)
310
                {
311
                    local orig;
312
313
                    /* 
314
                     *   we are going to show a destination name for this
315
                     *   item, so set the special option flag to let the
316
                     *   lister know that this is the case 
317
                     */
318
                    options |= ExitLister.hasDestNameFlag;
319
320
                    /* 
321
                     *   if this is the back-to connector, note that we
322
                     *   know the name of the back-to location 
323
                     */
324
                    if (destIsBack)
325
                        options |= ExitLister.hasBackNameFlag;
326
                    
327
                    /* 
328
                     *   If this destination name already appears in the
329
                     *   list, don't include this one in the list.
330
                     *   Instead, add this direction to the 'others' list
331
                     *   for the existing entry, so that we will show each
332
                     *   known destination only once. 
333
                     */
334
                    orig = destList.valWhich({x: x.dest_ == dest});
335
                    if (orig != nil)
336
                    {
337
                        /* 
338
                         *   this same destination name is already present
339
                         *   - add this direction to the existing entry's
340
                         *   list of other directions going to the same
341
                         *   place 
342
                         */
343
                        orig.others_ += dir;
344
345
                        /* 
346
                         *   if this is the back-to connector, note it in
347
                         *   the original destination item 
348
                         */
349
                        if (destIsBack)
350
                            orig.destIsBack_ = true;
351
352
                        /* 
353
                         *   don't add this direction to the main list,
354
                         *   since we don't want to list the destination
355
                         *   redundantly 
356
                         */
357
                        continue;
358
                    }
359
                }
360
361
                /* add it to our list */
362
                destList.append(new DestInfo(dir, dest, destName,
363
                                             destIsBack));
364
            }
365
        }
366
367
        /* show the list */
368
        lister.showListAll(destList.toList(), options, 0);
369
    }
370
;
371
372
/*
373
 *   A destination tracker.  This keeps track of a direction and the
374
 *   apparent destination in that direction. 
375
 */
376
class DestInfo: object
377
    construct(dir, dest, destName, destIsBack)
378
    {
379
        /* remember the direction, destination, and destination name */
380
        dir_ = dir;
381
        dest_ = dest;
382
        destName_ = destName;
383
        destIsBack_ = destIsBack;
384
    }
385
386
    /* the direction of travel */
387
    dir_ = nil
388
389
    /* the destination room object */
390
    dest_ = nil
391
392
    /* the name of the destination */
393
    destName_ = nil
394
395
    /* flag: this is the "back to" destination */
396
    destIsBack_ = nil
397
398
    /* list of other directions that go to our same destination */
399
    others_ = []
400
;
401
402
/*
403
 *   Settings item - show defaults in status line 
404
 */
405
exitsMode: SettingsItem
406
    /* our ID */
407
    settingID = 'adv3.exits'
408
409
    /* show our description */
410
    settingDesc =
411
        (gLibMessages.currentExitsSettings(inStatusLine, inRoomDesc))
412
413
    /* convert to text */
414
    settingToText()
415
    {
416
        /* just return the two binary variables */
417
        return (inStatusLine ? 'on' : 'off')
418
            + ','
419
            + (inRoomDesc ? 'on' : 'off');
420
    }
421
422
    settingFromText(str)
423
    {
424
        /* parse out our format */
425
        if (rexMatch('<space>*(<alpha>+)<space>*,<space>*(<alpha>+)',
426
                     str.toLower()) != nil)
427
        {
428
            /* pull out the two variables from the regexp groups */
429
            inStatusLine = (rexGroup(1)[3] == 'on');
430
            inRoomDesc = (rexGroup(2)[3] == 'on');
431
        }
432
    }
433
434
    /* 
435
     *   Our value is in two parts.  inStatusLine controls whether or not
436
     *   we show the exit list in the status line; inRoomDesc controls the
437
     *   exit listing in room descriptions.  
438
     */
439
    inStatusLine = true
440
    inRoomDesc = nil
441
;
442