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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
/* 
4
 *   Copyright (c) 2000, 2006 Michael J. Roberts.  All Rights Reserved. 
5
 *   
6
 *   TADS 3 Library - point of view
7
 *   
8
 *   This module provides definitions related to point of view and sensory
9
 *   context.  When we generate output, we do so with respect to a
10
 *   particular point of view; different points of view can result in
11
 *   different output, because of the viewer's distance from an object, for
12
 *   example, or because of the presence of obscuring materials between the
13
 *   viewer and the viewed object.  We also generate output in a particular
14
 *   sensory context, which controls whether or not a message that
15
 *   describes an object with respect to a particular sense should be
16
 *   generated at all; for example, if the viewer can't see an object
17
 *   because of darkness or an obscuring layer of material, messages about
18
 *   the object's visual appearance should not be generated.  
19
 */
20
21
/* include the library header */
22
#include "adv3.h"
23
24
25
/* ------------------------------------------------------------------------ */
26
/*
27
 *   Call a function with a given sensory context.
28
 *   
29
 *   The sensory context specifies the source of any messages generated in
30
 *   the course of the routine we invoke and the sense which those
31
 *   messages use to convey information.  If the player character cannot
32
 *   sense the source object in the given sense, then we block all
33
 *   messages generated while calling this function.
34
 *   
35
 *   If the source object is nil, this establishes a neutral sense context
36
 *   in which all messages are visible.
37
 *   
38
 *   This can be used for processing events that are not directly
39
 *   initiated by the player character, such as non-player character
40
 *   activities or scheduled events (fuses and daemons).  The idea is that
41
 *   anything described in the course of calling our routine is physically
42
 *   associated with the source object and relates to the given sense, so
43
 *   if the player character cannot sense the source object, then the
44
 *   player should not be aware of these happenings and thus should not
45
 *   see the messages.
46
 *   
47
 *   Sense contexts are not nested in their effects - we will show or hide
48
 *   the messages that our callback routine generates regardless of
49
 *   whether or not messages are hidden by an enclosing sensory context.
50
 *   So, this routine effectively switches to the new sense context for
51
 *   the duration of the callback, eliminating the effect of any enclosing
52
 *   context.  However, we do restore the enclosing sense context before
53
 *   returning, so there is no lasting net effect on the global sense
54
 *   context.  
55
 */
56
callWithSenseContext(source, sense, func)
57
{
58
    return senseContext.withSenseContext(source, sense, func);
59
}
60
61
/*
62
 *   Sense context output filter.  When the sense context doesn't allow
63
 *   the player character to sense whatever's going on, we'll block all
64
 *   output; otherwise, we'll pass output through unchanged.  
65
 */
66
senseContext: SwitchableCaptureFilter
67
    /*
68
     *   Recalculate the current sense context.  We will check to see if
69
     *   the player character can sense the current sense context's source
70
     *   object in the current sense context's sense, and show or hide
71
     *   output from this point forward accordingly.  This can be called
72
     *   any time conditions change in such a way that the sense context
73
     *   should be refigured.  
74
     */
75
    recalcSenseContext()
76
    {
77
        /* 
78
         *   simply invalidate the cached status; this will ensure that we
79
         *   recalculate the status the next time we're called upon to
80
         *   determine whether or not we need to block output 
81
         */
82
        cached_ = nil;
83
    }
84
85
    /* 
86
     *   Get our current blocking status.  If we've already cached the
87
     *   status, we'll return the cached status; otherwise, we'll compute
88
     *   and cache the new blocking status, based on the current sensory
89
     *   environment. 
90
     */
91
    isBlocking()
92
    {
93
        /* if we haven't cached the status, compute the new status */
94
        if (!cached_)
95
        {
96
            /* calculate the new status based on the current environment */
97
            isBlocking_ = shouldBlock();
98
99
            /* we now have a valid cached status */
100
            cached_ = true;
101
        }
102
103
        /* return the cached status */
104
        return isBlocking_;
105
    }
106
107
    /* our current cached blocking status, and its validity */
108
    isBlocking_ = nil
109
    cached_ = true
110
111
    /*
112
     *   Calculate whether or not I should be blocking output according to
113
     *   the current game state.  Returns true if so, nil if not.  
114
     */
115
    shouldBlock()
116
    {
117
        /*
118
         *   Determine if the new sense context allows messages to be
119
         *   displayed.  If there is no source object, we allow
120
         *   everything; otherwise, we only allow messages if the player
121
         *   character can sense the source object in the given sense.  
122
         */
123
        if (source_ == nil)
124
        {
125
            /* neutral sense context - allow messages */
126
            return nil;
127
        }
128
        else
129
        {
130
            /* 
131
             *   Determine if the player character can sense the given
132
             *   object.  If the source can be sensed with any degree of
133
             *   transparency other than 'opaque', allow the messages.  
134
             */
135
            return (libGlobal.playerChar.senseObj(sense_, source_)
136
                    .trans == opaque);
137
        }
138
    }
139
140
    /* invoke a callback with a given sense context */
141
    withSenseContext(source, sense, func)
142
    {
143
        local oldSource, oldSense;
144
145
        /* remember the old sense and source values */
146
        oldSource = source_;
147
        oldSense = sense_;
148
149
        /* set up the new sense context */
150
        setSenseContext(source, sense);
151
152
        /* make sure we restore the old status on the way out */
153
        try
154
        {
155
            /* invoke the callback */
156
            return (func)();
157
        }
158
        finally
159
        {
160
            /* restore the old sense context */
161
            setSenseContext(oldSource, oldSense);
162
        }
163
    }
164
165
    /*
166
     *   Set a sense context. 
167
     */
168
    setSenseContext(source, sense)
169
    {
170
        /* remember the new setings */
171
        source_ = source;
172
        sense_ = sense;
173
174
        /* calculate the new sensory status */
175
        recalcSenseContext();
176
    }
177
178
    /* the source object and sense of the sensory context */
179
    sense_ = nil
180
    source_ = nil
181
;
182
183
/* ------------------------------------------------------------------------ */
184
/*
185
 *   Get the current point-of-view actor - this is the actor who's
186
 *   performing the action (LOOK AROUND, EXAMINE, SMELL, etc) that's
187
 *   generating the current description.  
188
 */
189
getPOVActor()
190
{
191
    return libGlobal.pointOfViewActor;
192
}
193
194
/*
195
 *   Get the current point of view.  In *most* cases, this is the same as
196
 *   the point-of-view actor: the actor is looking around with its own
197
 *   eyes, so it's the point of view.  However, this can differ from the
198
 *   actor when the actor is viewing the location being described through
199
 *   an intermediary of some kind.  For example, if an actor is observing a
200
 *   remote room through a closed-circuit TV system, the point of view
201
 *   would be the camera in the remote room (not the TV - the point of view
202
 *   is intended to be the object that's physically absorbing the light
203
 *   rays or other sensory equivalents).  
204
 */
205
getPOV()
206
{
207
    return libGlobal.pointOfView;
208
}
209
210
/* get the POV actor, returning the given default if there isn't one set */
211
getPOVActorDefault(dflt)
212
{
213
    /* start with the global setting */
214
    local val = libGlobal.pointOfViewActor;
215
216
    /* if that's not nil, return it; otherwise, return the default */
217
    return (val != nil ? val : dflt);
218
}
219
220
/* get the POV, returning the given default if there isn't one set */
221
getPOVDefault(dflt)
222
{
223
    /* start with the global setting */
224
    local val = libGlobal.pointOfView;
225
226
    /* if that's not nil, return it; otherwise, return the default */
227
    return (val != nil ? val : dflt);
228
}
229
230
/*
231
 *   Change the point of view without altering the point-of-view stack 
232
 */
233
setPOV(actor, pov)
234
{
235
    /* set the new point of view */
236
    libGlobal.pointOfViewActor = actor;
237
    libGlobal.pointOfView = pov;
238
}
239
240
/*
241
 *   Set the root point of view.  This doesn't affect the current point of
242
 *   view unless there is no current point of view; this merely sets the
243
 *   outermost default point of view.  
244
 */
245
setRootPOV(actor, pov)
246
{
247
    local stk = libGlobal.povStack;
248
    
249
    /* 
250
     *   if there's nothing in the stacked list, set the current point of
251
     *   view; otherwise, just set the innermost stacked element 
252
     */
253
    if (stk.length() == 0)
254
    {
255
        /* there is no point of view, so set the current point of view */
256
        libGlobal.pointOfViewActor = actor;
257
        libGlobal.pointOfView = pov;
258
    }
259
    else
260
    {
261
        /* set the innermost stacked point of view */
262
        stk[1] = pov;
263
        stk[2] = actor;
264
    }
265
}
266
267
/*
268
 *   Push the current point of view
269
 */
270
pushPOV(actor, pov)
271
{
272
    /* stack the current one */
273
    libGlobal.povStack.append(libGlobal.pointOfView);
274
    libGlobal.povStack.append(libGlobal.pointOfViewActor);
275
276
    /* set the new point of view */
277
    setPOV(actor, pov);
278
}
279
280
/*
281
 *   Pop the most recent point of view pushed 
282
 */
283
popPOV()
284
{
285
    local stk = libGlobal.povStack;
286
    local len;
287
    
288
    /* check if there's anything left on the stack */
289
    len = stk.length();
290
    if (len != 0)
291
    {
292
        /* take the most recent element off the stack */
293
        libGlobal.pointOfViewActor = stk[len];
294
        libGlobal.pointOfView = stk[len - 1];
295
296
        /* take the actor and POV objects off the stack */
297
        stk.removeRange(len - 1, len);
298
    }
299
    else
300
    {
301
        /* nothing on the stack - clear the point of view */
302
        libGlobal.pointOfViewActor = nil;
303
        libGlobal.pointOfView = nil;
304
    }
305
}
306
307
/*
308
 *   Clear the point of view and all stacked elements
309
 */
310
clearPOV()
311
{
312
    local len;
313
    local stk = libGlobal.povStack;
314
    
315
    /* forget the current point of view */
316
    setPOV(nil, nil);
317
318
    /* drop everything on the stack */
319
    len = stk.length();
320
    stk.removeRange(1, len);
321
}
322
323
/*
324
 *   Call a function from a point of view.  We'll set the new point of
325
 *   view, call the function with the given arguments, then restore the
326
 *   original point of view. 
327
 */
328
callFromPOV(actor, pov, funcToCall, [args])
329
{
330
    /* push the new point of view */
331
    pushPOV(actor, pov);
332
333
    /* make sure we pop the point of view no matter how we leave */
334
    try
335
    {
336
        /* call the function */
337
        (funcToCall)(args...);
338
    }
339
    finally
340
    {
341
        /* restore the enclosing point of view on the way out */
342
        popPOV();
343
    }
344
}
345
346