cfad47cfa3/t3compiler/tads3/lib/extensions/subtime.t

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
/*
2
 *   Copyright 2003, 2006 Michael J. Roberts
3
 *   
4
 *   "Subjective Time" module.  This implements a form of in-game
5
 *   time-keeping that attempts to mimic the player's subjective
6
 *   experience of time passing in the scenario while still allowing for
7
 *   occasional, reasonably precise time readings, such as from a
8
 *   wristwatch in the game world.
9
 *   
10
 *   It's always been difficult to handle the passage of time in
11
 *   interactive fiction.  It's such a vexing problem that most IF avoids
12
 *   the problem entirely by setting the game action in a world that
13
 *   forgot time, where there are no working clocks and where night never
14
 *   falls.  Like so many of the hard parts of interactive fiction, the
15
 *   difficulty here comes from the uneasy union of simulation and
16
 *   narrative that's at the heart of all IF.
17
 *   
18
 *   In ordinary static narratives, the passage of time can swing between
19
 *   extremes: a scene that only takes moments of real-time within the
20
 *   story, such as an intense action scene or an extended inner
21
 *   monologue, can go on for many pages; and elsewhere in the same story,
22
 *   days and years can pass in the space of a sentence or two.  Narrative
23
 *   time has no relationship to the length of a passage of text;
24
 *   narrative time is related only to the density of story-significant
25
 *   action.
26
 *   
27
 *   It might seem at first glance that this break between in-story time
28
 *   and chapter length is a special privilege of the staticness of a
29
 *   book: the text just sits there on a page, unchanging in time, so of
30
 *   course there's no real-time anchor for the story's internal clock.
31
 *   But that can't be it, because we see exactly the same narrative
32
 *   manipulation of time in film, which is a fundamentally time-based
33
 *   medium.  Films don't present their action in real-time any more than
34
 *   books do.  What's more, film follows the same rules as text-based
35
 *   fiction in compressing and expanding story-time: many minutes of film
36
 *   can spool by as the last seven seconds tick off a bomb's count-down
37
 *   timer, while the seasons can change in the course of a five-second
38
 *   dissolve.  Even Fox's "24," a television series whose entire conceit
39
 *   is that the action is presented in real-time, can be seen on closer
40
 *   inspection to hew to its supposed real-time anchors only every ten
41
 *   minutes or so, where the ads are inserted; in between the ads, the
42
 *   normal bag of tricks is liberally employed to compress and expand
43
 *   time.
44
 *   
45
 *   In simulations, we have a different situation, because the pacing of
46
 *   the story is under the indirect control of the player.  (I call the
47
 *   control indirect because it's not presented to the player as a simple
48
 *   control knob with settings for "slower" and "faster."  The player can
49
 *   directly control how quickly she enters commands, but this has only a
50
 *   very minor effect on the pace of the story; the major component of
51
 *   the story's pacing is the player's rate of absorbing information from
52
 *   the story and synthesizing solutions to its obstacles.  In most
53
 *   cases, this is the gating factor: the player is motivated, by a
54
 *   desire to win the game, to go as fast as possible, but is unable to
55
 *   go any faster because of the need to peform time-consuming mental
56
 *   computation.  Thus the player's control is indirect; there's the
57
 *   illusion of a control knob for "faster" and "slower," but in practice
58
 *   the knob's setting is forced to a fixed value, determined by the
59
 *   player's ability to solve puzzles: no player ever wants to choose a
60
 *   "slower" setting than they have to, and the "faster" settings are
61
 *   unavailable because of the gating mental computation factor.)
62
 *   
63
 *   But even so, the player in a simulation does have complete control
64
 *   over the specific actions that the player character performs.  The
65
 *   player makes the character walk around, examine things, try a few
66
 *   random verbs on an object, walk around some more, and so on.  All of
67
 *   these things clearly ought to take time in the simulation.  The
68
 *   question is: how much time?
69
 *   
70
 *   The most obvious approach is to tie the passage of game-time to the
71
 *   actions the player character performs, by making each turn take some
72
 *   amount of time.  At the simplest, each turn could take a fixed amount
73
 *   of time, say 1 minute.  A more sophisticated approach might be for
74
 *   some kinds of actions to take more time than others; walking to a new
75
 *   room might take 2 minutes, say, while examining a wall might take 30
76
 *   seconds.  Some early IF games took this approach.  Infocom's
77
 *   Planetfall, for example, has a cycle of day and night that's tied to
78
 *   the number of turns taken by the player.
79
 *   
80
 *   Unfortunately, such a direct mapping of turns to game-time hasn't
81
 *   proved to be very satisfactory in practice.  This approach doesn't
82
 *   seem to make the treatment of time more realistic -- in fact, it's
83
 *   just the opposite.  For example, in Planetfall, as objective
84
 *   game-time passes, it's necessary to eat and sleep; the game has a
85
 *   limited supply of food, so if you spend too much time exploring, you
86
 *   can lock yourself out of victory simply by exhausting all available
87
 *   food and starving to death.  The workaround, of course, is to restart
88
 *   the game after you've explored enough to know your way around, at
89
 *   which point you can cruise through the parts you've been through
90
 *   before.  The end result is that we get two opposite but equally
91
 *   unrealistic treatments of time: on the first pass, far too much
92
 *   objective time passes as we wander around exploring, given how little
93
 *   we accomplish; and on the second pass, far too little objective time
94
 *   elapses, given how rapidly we progress through the story.
95
 *   
96
 *   One problem would seem to be that verbs are far too coarse a means to
97
 *   assign times to actions: picking up five small items off a table
98
 *   might take almost no time at all, while picking up the table itself
99
 *   could require a minute or two; walking to the next room might take
100
 *   ten seconds, while walking across an airport terminal takes ten
101
 *   minutes.  An author could in principle assign timings to actions at
102
 *   the level of individual verb-object combinations, but this would
103
 *   obviously be unmanageable in anything but the tiniest game.  
104
 *   
105
 *   A second problem is that it might be better to see the player's
106
 *   actual sequence of command input as somewhat distinct from the player
107
 *   character's subjective storyline.  In the turns-equals-time approach,
108
 *   we're essentially asserting a literal equivalence between the
109
 *   player's commands and the player character's story.  In typical IF
110
 *   game play, the player spends quite a lot of time exploring,
111
 *   examining, and experimenting.  If we were to imagine ourselves as an
112
 *   observer inside the game world, what we'd see would be highly
113
 *   unnatural and unrealistic: no one actually behaves like an IF player
114
 *   character.  If we wanted to achieve more realism, we might consider
115
 *   that the medium imposes a certain amount of this exploration activity
116
 *   artificially, as a means for the player to gather information that
117
 *   would, in reality, be immediately and passively accessible to the
118
 *   player character - just by being there, the player character would
119
 *   instantly absorb a lot of information that the player can only learn
120
 *   by examination and experimentation.  Therefore, we could say that,
121
 *   realistically, a lot of the exploration activity that a player
122
 *   performs isn't really part of the story, but is just an artifact of
123
 *   the medium, and thus shouldn't count against the story world's
124
 *   internal clock.
125
 *   
126
 *   One ray of hope in all of this is that the human sense of time is
127
 *   neither precise nor objective.  People do have an innate sense of the
128
 *   passage of time, but it doesn't work anything like the
129
 *   turns-equals-time approach would have it.  People don't internally
130
 *   track time by counting up the actions they perform.  In fact, people
131
 *   generally don't even have a very precise intuitive sense for how long
132
 *   a particular action takes; in most cases, people are actually pretty
133
 *   bad at estimating the time it will take to perform ordinary tasks.
134
 *   (Unless you're asking about areas where they have a lot of hands-on
135
 *   experience actually timing things on a clock, such as you might find
136
 *   in certain professions.  And a lot of people aren't even good at that
137
 *   - just try getting a decent schedule estimate out of a programmer,
138
 *   for example.)
139
 *   
140
 *   The human sense of time is not, then, an objective accounting of
141
 *   recent activity.  Rather, it's a highly subjective sense, influenced
142
 *   by things like emotion and focus of attention.  A couple of familiar
143
 *   examples illustrate how a person's subjective experience of time can
144
 *   wildly depart from time's objective passage.  First, think about how
145
 *   time seems to stretch out when you have nothing to do but wait for
146
 *   something, like when you're stuck in an airport waiting for a late
147
 *   flight.  Second, think about how time flies by when you're doing
148
 *   something that intensely focuses your attention, like when you're
149
 *   playing a really good computer game or you're deeply engaged in a
150
 *   favorite hobby: hours can seem to disappear into a black hole.  Being
151
 *   bored tends to expand time, while being intensely occupied tends to
152
 *   compress it.  There are other factors that affect the subjective time
153
 *   sense, but occupation of attention seems to be one of the most
154
 *   important.
155
 *   
156
 *   So where does this lead us?  If it's not clear which "turns" are part
157
 *   of the story and which are merely artifacts of the medium, the turn
158
 *   counter can't be a reliable source of game-time.  But if the human
159
 *   sense of time is inherently subjective, then maybe we're better off
160
 *   tying the passage of time to something other than the raw turn
161
 *   counter anyway.
162
 *   
163
 *   The key to our solution is to treat the turn counter as correlated to
164
 *   the player's subjective time, rather than the story's objective time.
165
 *   In other words, rather than saying that a LOOK command actually takes
166
 *   30 seconds in the game world, we say that a LOOK command might *feel*
167
 *   like it takes some amount of time to the player, but that the actual
168
 *   time it takes depends on what it accomplishes in the story.  How much
169
 *   time really passes for a single LOOK command?  It all depends on how
170
 *   many LOOK commands it takes to accomplish something.
171
 *   
172
 *   So, if the player spends long periods of time exploring and frobbing
173
 *   around with objects, but not making any progress, we've had a lot of
174
 *   subjective time pass (many turns), but very little story time (few
175
 *   story-significant events).  This is exactly what people are used to
176
 *   in real life: when you're bored and not accomplishing anything, time
177
 *   seems to drag.  If the player starts knocking down puzzles left and
178
 *   right, making great progress through the story, we suddenly have a
179
 *   feeling of little subjective time passing (few turns) while story
180
 *   time is flying past (many story-significant events).  Again, just
181
 *   what we're used to in real life when deeply engaged in some activity.
182
 *   
183
 *   This basic approach isn't new.  Gareth Rees's Christminster includes
184
 *   plot elements that occur at particular clock-times within the story,
185
 *   and a working clock-tower to let the player character tell the time.
186
 *   Time advances in the game strictly according to the player's progress
187
 *   through the plot (i.e., solving puzzles).  At key plot events, the
188
 *   clock advances; at all other times, it just stays fixed in place.
189
 *   The game works around the "precision problem" (which we'll come to
190
 *   shortly) with the somewhat obvious contrivance of omitting the
191
 *   minute-hand from the clock, so the time can only be read to the
192
 *   nearest hour.  Michael Gentry's Anchorhead also ties events to the
193
 *   story's clock-time, and provides a story clock of sorts that advances
194
 *   by the player's progress in solving puzzles.  Anchorhead handles the
195
 *   precision problem by using an even coarser clock than Christminster,
196
 *   specifically the position of the sun in the sky, which I think can
197
 *   only be read to a precision of "day" or "night."  The
198
 *   precision-avoiding contrivance here is much less obvious than in
199
 *   Christminster, and it especially helps that the setting and
200
 *   atmosphere of the game practically demand a continuous dark overcast.
201
 *   
202
 *   But what if we wanted a normal clock in the game - one with a minute
203
 *   hand?  This is where the precision problem comes in.  The problem
204
 *   with subjective time is that, in the real world, a tool-using human
205
 *   in possession of a wristwatch can observe the objective time whenever
206
 *   she wants.  In games like Anchorhead and Christminster, the clock
207
 *   only advances at key plot points, so if we permitted the player
208
 *   character a timepiece with a minute hand, we'd get unrealistic
209
 *   exchanges like this:
210
 *   
211
 *   >x watch
212
 *.  It's 3:05pm.
213
 *   
214
 *   >east. get bucket. west. fill bucket with water. drink water. east.
215
 *.  ... done ...
216
 *   
217
 *   >etc, etc, etc...
218
 *.  ... done ...
219
 *   
220
 *   >x watch
221
 *.  It's 3:05pm.
222
 *   
223
 *   That's why Christminster doesn't have a minute hand, and why
224
 *   Anchorhead doesn't have clocks at all.  When the time is only
225
 *   reported vaguely, the player won't notice that the clock is frozen
226
 *   between plot events: it's not a problem when we're told that it's
227
 *   still "some time after three" for dozens of turns in a row.
228
 *   
229
 *   This is where this implementation tries to add something new.  We try
230
 *   to achieve a compromise between an exclusively narrative advancement
231
 *   of time, and an exclusively turn-based clock.  The goal is to have
232
 *   the story drive the actual advancement of the clock, while still
233
 *   giving plausible time readings whenever the player wants them.
234
 *   
235
 *   The compromise hinges on the way people use clocks in the real world:
236
 *   a person will typically glance at the clock occasionally, but not
237
 *   watch it continuously.  Our approach also depends upon the
238
 *   observation that people are generally bad at estimating how long a
239
 *   particular activity takes.  If people are bad at judging the time for
240
 *   one activity, then they're even worse at judging it for a series of
241
 *   activities.  This is an advantage for us, because it means that the
242
 *   "error bars" around a player's estimate of how long something ought
243
 *   to be taking are inherently quite large, which in turn means that we
244
 *   have a large range of plausible results - as long as we don't say
245
 *   it's 3:05pm every time we're asked.
246
 *   
247
 *   Here's the scheme.  We start by having the game assign the times of
248
 *   important events.  The game doesn't have to tell us in advance about
249
 *   everything; it only has to tell us the starting time, and the game
250
 *   clock time of the next important event in the plot.  For example, the
251
 *   game could tell us that it's now noon, and the next important plot
252
 *   point will be when the player character manages to get into the
253
 *   castle, which happens at 6pm, just as the sun is starting to set.
254
 *   Note that the story asserts that this event happens at 6pm; it
255
 *   doesn't matter how many turns it takes the player to solve the
256
 *   puzzles and get into the castle, since *in the story*, we always get
257
 *   in at 6pm.  
258
 *   
259
 *   If the player looks at a clock at the start of the game, they'll see
260
 *   that it's noon.  The clock module then starts keeping track of the
261
 *   number of turns the player takes.  The next time the player looks at
262
 *   a clock, we'll check to see how many turns it's been since the last
263
 *   look at the clock.  (We'll also consider plot points where the story
264
 *   actually sets the clock to be the same as the player looking at the
265
 *   clock, since these have the same effect of making us commit to a
266
 *   particular time.)  We'll scale this linearly to a number of minutes
267
 *   passing.  Then, we'll crank down this linear scaling factor according
268
 *   to how close we're getting to the next event time - this ensures that
269
 *   we'll leave ourselves room to keep the clock advancing without
270
 *   bumping up against the next event time.  So, we have a sort of Zeno's
271
 *   paradox factor: the closer we get to the next event time, the slower
272
 *   we'll approach it.
273
 *   
274
 *   The point is to create a plausible illusion of precision.  A player
275
 *   who checks infrequently, as should be typical, should see a plausible
276
 *   series of intermediate times between major plot points.
277
 *   
278
 *   One final note: if you want to display a clock in the status line, or
279
 *   show the current time with every prompt, this is the wrong module to
280
 *   use.  A key assumption of the scheme implemented here is that the
281
 *   time will be checked only occasionally, when it occurs to the player
282
 *   to look.  If the game is constantly checking the time
283
 *   programmatically, it'll defeat the whole purpose, since we won't be
284
 *   able to exploit the player's presumed uncertainty about exactly how
285
 *   much time should have elapsed between checks.  
286
 */
287
288
#include <tads.h>
289
290
291
/* ------------------------------------------------------------------------ */
292
/*
293
 *   The Clock Manager is the object that keeps track of the game-world
294
 *   wall-clock time.  Timepieces in the game can consult this for the
295
 *   current official time.
296
 *   
297
 *   To use the clock manager's services, you should decide on a set of
298
 *   plot events in your game that occur at particular times.  This
299
 *   requires you to "linearize" your game's plot to the extent that these
300
 *   events must occur in a particular order and at particular times within
301
 *   the game world.  This might sound at odds with the idea that the
302
 *   player is in control, but most IF stories are actually structured this
303
 *   way anyway, with at least a few plot points that always happen in a
304
 *   particular order, no matter what the player does.  Puzzles tend to
305
 *   introduce a locally linear structure by their nature, in that a player
306
 *   can't reach a plot point that depends on the solution to a puzzle
307
 *   until solving that puzzle.
308
 *   
309
 *   Once you've decided on your important plot points, create a ClockEvent
310
 *   object to represent each one.  Each ClockEvent object specifies the
311
 *   game-clock time at which the event occurs.  Once you've defined these
312
 *   objects, add code to your game to call the eventReached() method of
313
 *   the appropriate ClockEvent object when each plot event occurs.  This
314
 *   anchors the game-world clock to the player's progress through the
315
 *   special set of plot points.
316
 *   
317
 *   Note that you must always create a ClockEvent to represent the very
318
 *   beginning of the game - this establishes the time at the moment the
319
 *   game opens.  At start-up, we'll initialize the game clock to the time
320
 *   of the earliest event we find, so it's never necessary to call
321
 *   eventReached() on this special first event - its function is to tell
322
 *   the clock manager the game's initial wall-clock time.
323
 *   
324
 *   We keep track of the "day" in game time, but we don't have a calendar
325
 *   feature.  We simply keep track of the day relative to the moment the
326
 *   game begins; it's up to the game to make this into a calendar date, if
327
 *   desired.  For example, if the game is designed to begin at noon on
328
 *   March 21, 1882, the game could figure out the current calendar day by
329
 *   adding the current day to March 20 (since the first day is day 1).
330
 *   The game would have to figure out when month and year boundaries are
331
 *   crossed, of course, to show the resulting calendar date.  If you don't
332
 *   care about tying your story to a particular calendar date, but you do
333
 *   want to nail it down to particular days of the week, this is a lot
334
 *   easier, since you can use "mod-7" arithmetic to compute the weekday -
335
 *   just use "(day % 7) + 1" as an index into a seven-element list of
336
 *   weekday names.
337
 *   
338
 *   A recommendation on usage: if you're using the clock for scheduling
339
 *   appointments that the player character is responsible for keeping,
340
 *   it's a good idea to structure the game so that there's a plot event a
341
 *   little before an appointment.  For example, if you've told the player
342
 *   that they have a noon lunch planned with a friend, you should set up
343
 *   the plot sequence so that there's some important event that happens at
344
 *   11:45am, or thereabouts.  The event doesn't need to be related to the
345
 *   lunch in any visible way, as far as the player is concerned - the
346
 *   point of the event is to gate the lunch appointment.  The event should
347
 *   usually be something of the nature of solving a puzzle.  This way, the
348
 *   player works on the puzzle, since it's not yet time for the lunch
349
 *   appointment; suddenly, when they solve the puzzle, the game can let
350
 *   them know that it's almost lunch time, and that they should go meet
351
 *   their friend.  This plays well into the whole subjective-time design
352
 *   of the clock manager, because the player will presumably have her
353
 *   attention occupied solving the puzzle, and so it will seem perfectly
354
 *   natural for the game clock to have changed substantially when she
355
 *   comes up for air after having been working on the puzzle.  You can
356
 *   then set up another plot event to represent the player character's
357
 *   arrival at the restaurant, which might occur in the story at 11:55am
358
 *   (or maybe 12:20pm, if the PC is someone who tends to annoy his friends
359
 *   by always running late).
360
 *   
361
 *   When the player character isn't supposed to be waiting for a
362
 *   particular plot event, there's no need to gate it like this.  If you
363
 *   have a plot event that coincides with night falling, 
364
 *   
365
 *   Note that you can use the clock manager even if your game has no
366
 *   pre-defined plot points.  Just create one event to represent the start
367
 *   of the game, to give the clock its initial setting.  The clock manager
368
 *   will use the same subjective time scheme it would if there were more
369
 *   plot points, but with no ending boundary to worry about.  The clock
370
 *   manager probably isn't as interesting in a game without significant
371
 *   time-anchored plot events, since the passage of time doesn't have any
372
 *   real relation to the plot in such a game;s but it might be desirable
373
 *   to have a working clock anyway, if only for the added sense of detail.
374
 */
375
clockManager: PreinitObject
376
    /*
377
     *   Get the current game-clock time.  This returns a list in the same
378
     *   format as ClockEvent.eventTime: [day,hour,minute].
379
     *   
380
     *   Remember that our time-keeping scheme is a sort of "Schrodinger's
381
     *   clock" [see footnote 1].  Between time checks, the game time
382
     *   clock is in a vague, fuzzy state, drifting along at an
383
     *   indeterminate pace from the most recent check.  When this method
384
     *   is called, though, the clock manager is forced to commit to a
385
     *   particular time, because we have to give a specific answer to the
386
     *   question we're being asked ("what time is it?").  As in quantum
387
     *   mechanics, then, the act of observation affects the quantity
388
     *   being observed.  Therefore, you should avoid calling this routine
389
     *   unnecessarily; call it only when you actually have to tell the
390
     *   player what time it is - and don't tell the player what time it
391
     *   is unless they ask, or there's some other good reason.
392
     *   
393
     *   If you want a string-formatted version of the time (as in
394
     *   '9:05pm'), you can call checkTimeFmt().  
395
     */
396
    checkTime()
397
    {
398
        local turns;
399
        local mm;
400
        
401
        /* 
402
         *   Determine how many turns it's been since we last committed to
403
         *   a specific wall-clock time.  This will give us the
404
         *   psychological "scale" of the amount of elapsed wall-clock the
405
         *   user might expect.  
406
         */
407
        turns = Schedulable.gameClockTime - turnLastCommitted;
408
409
        /* 
410
         *   start with the base scaling factor - this is the number of
411
         *   minutes of game time we impute to a hundred turns, in the
412
         *   absence of the constraint of running up against the next event
413
         */
414
        mm = (turns * baseScaleFactor) / 100;
415
416
        /*
417
         *   If the base scaled time would take us within two hours of the
418
         *   next event time, slow the clock down from our base scaling
419
         *   factor so that we always leave ourselves room to advance the
420
         *   clock further on the next check.  Reduce the passage of time
421
         *   in proportion to our reduced window - so if we have only 60
422
         *   minutes left, advance time at half the normal pace.  
423
         */
424
        if (nextTime != nil)
425
        {
426
            /* get the minutes between now and the next scheduled event */
427
            local delta = diffMinutes(nextTime, curTime);
428
429
            /* check to see if the raw increment would leave under 2 hours */
430
            if (delta - mm < 120)
431
            {
432
                /*
433
                 *   The raw time increment would leave us under two hours
434
                 *   away.  If we have under two hours to go before the
435
                 *   next event, scale down the rate of time in proportion
436
                 *   to our share under two hours.  (Note that we might
437
                 *   have more than two hours to go and still be here,
438
                 *   because the raw adjusted time leaves under two
439
                 *   hours.)  
440
                 */
441
                if (delta < 120)
442
                    mm = (mm * delta) / 120;
443
444
                /* 
445
                 *   In any case, cap it at half the remaining time, to
446
                 *   ensure that we won't ever make it to the next event
447
                 *   time until the next event occurs.
448
                 */
449
                if (mm > delta / 2)
450
                    mm = delta / 2;
451
            }
452
        }
453
454
        /* 
455
         *   If our calculation has left us with no passage of time, simply
456
         *   return the current time unchanged, and do not treat this as a
457
         *   commit point.  We don't consider this a commit point because
458
         *   we treat it as not even checking again - it's effectively just
459
         *   a repeat of the last check, since it's still the same time.
460
         *   This ensures that we won't freeze the clock for good due to
461
         *   rounding - enough additional turns will eventually accumulate
462
         *   to nudge the clock forward.  
463
         */
464
        if (mm == 0)
465
            return curTime;
466
467
        /* add the minutes to the current time */
468
        curTime = addMinutes(curTime, mm);
469
470
        /* the current turn is now the last commit point */
471
        turnLastCommitted = Schedulable.gameClockTime;
472
473
        /* return the new time */
474
        return curTime;
475
    }
476
477
    /*
478
     *   The base scaling factor: this is the number of minutes per hundred
479
     *   turns when we have unlimited time until the next event.  This
480
     *   number is pretty arbitrary, since we're depending so much on the
481
     *   player's uncertainty about just how long things take, and also
482
     *   because we'll adjust it anyway when we're running out of time
483
     *   before the next event.  Even so, you might want to adjust this
484
     *   value up or down according to your sense of the pacing of your
485
     *   game.  
486
     */
487
    baseScaleFactor = 60
488
489
    /*
490
     *   Get the current game-clock time, formatted into a string with the
491
     *   given format mask - see formatTime() for details on how to write a
492
     *   mask string.
493
     *   
494
     *   Note that the same cautions for checkTime() apply here - calling
495
     *   this routine commits us to a particular time, so you should call
496
     *   this routine only when you're actually ready to display a time to
497
     *   the player.  
498
     */
499
    checkTimeFmt(fmt) { return formatTime(checkTime(), fmt); }
500
    
501
    /* 
502
     *   Get a formatted version of the given wall-clock time.  The time is
503
     *   expressed as a list, in the same format as ClockEvent.eventTime:
504
     *   [day,hour,minute], where 'day' is 1 for the first day of the game,
505
     *   2 for the second, and so on.
506
     *   
507
     *   The format string consists of one or more prefixes, followed by a
508
     *   format mask.  The prefixes are flags that control the formatting,
509
     *   but don't directly insert any text into the result string:
510
     *   
511
     *   24 -> use 24-hour time; if this isn't specified, a 12-hour clock
512
     *   is used instead.  On the 24-hour clock, midnight is hour zero, so
513
     *   12:10 AM is represented as 00:10.
514
     *   
515
     *   [am][pm] -> use 'am' as the AM string, and 'pm' as the PM string,
516
     *   for the 'a' format mask character.  This lets you specify an
517
     *   arbitrary formatting for the am/pm marker, overriding the default
518
     *   of 'am' or 'pm'.  For example, if you want to use 'A.M.' and
519
     *   'P.M.'  as the markers, you'd write a prefix of [A.M.][P.M.].  If
520
     *   you want to use ']' within the marker string itself, quote it with
521
     *   a '%': '[[AM%]][PM%]]' indicates markers of '[AM]' and '[PM]'.
522
     *   
523
     *   Following the prefix flags, you specify the format mask.  This is
524
     *   a set of special characters that specify parts of the time to
525
     *   insert.  Each special character is replaced with the corresponding
526
     *   formatted time information in the result string.  Any character
527
     *   that isn't special is just copied to the result string as is.  The
528
     *   special character are:
529
     *   
530
     *   h -> hour, no leading zero for single digits (hence 9:05, for
531
     *   example)
532
     *   
533
     *   hh -> hour, leading zero (09:05)
534
     *   
535
     *   m -> minutes, no leading zero (9:5)
536
     *   
537
     *   mm -> minutes with a leading zero (9:05)
538
     *   
539
     *   a -> AM/PM marker.  If an [am][pm] prefix was specified, the 'am'
540
     *   or 'pm' string from the prefix is used.  Otherwise, 'am' or 'pm'
541
     *   is literally inserted.
542
     *   
543
     *   % -> quote next character (so %% -> a single %)
544
     *   
545
     *   other -> literal
546
     *   
547
     *   Examples:
548
     *   
549
     *   'hh:mma' produces '09:05am'
550
     *.  '[A.M][P.M]h:mma' produces '9:05 P.M.'
551
     *.  '24hhmm' produces '2105'.  
552
     */
553
    formatTime(t, fmt)
554
    {
555
        local hh = t[2];
556
        local mm = t[3];
557
        local pm = (hh >= 12);
558
        local use24 = nil;
559
        local amStr = nil;
560
        local pmStr = nil;
561
        local ret;
562
        local match;
563
            
564
        /* check flags */
565
        for (;;)
566
        {
567
            local fl;
568
            
569
            /* check for a flag string */
570
            match = rexMatch(
571
                '24|<lsquare>(<^rsquare>|%%<rsquare>)+<rsquare>', fmt, 1);
572
573
            /* if we didn't find another flag, we're done */
574
            if (match == nil)
575
                break;
576
577
            /* pull out the flag text */
578
            fl = fmt.substr(1, match);
579
            fmt = fmt.substr(match + 1);
580
581
            /* check the match */
582
            if (fl == '24')
583
            {
584
                /* note 24-hour time */
585
                use24 = true;
586
            }
587
            else
588
            {
589
                /* it's an am/pm marker - strip the brackets */
590
                fl = fl.substr(2, fl.length() - 2);
591
592
                /* change any '%]' sequences into just ']' */
593
                fl = fl.findReplace('%]', ']', ReplaceAll, 1);
594
595
                /* set AM if we haven't set it already, else set PM */
596
                if (amStr == nil)
597
                    amStr = fl;
598
                else
599
                    pmStr = fl;
600
            }
601
        }
602
603
        /* if we didn't select an AM/PM, use the default */
604
        amStr = (amStr == nil ? 'am' : amStr);
605
        pmStr = (pmStr == nil ? 'pm' : pmStr);
606
607
        /* adjust for a 12-hour clock if we're using one */
608
        if (!use24)
609
        {
610
            /* subtract 12 from PM times */
611
            if (pm)
612
                hh -= 12;
613
614
            /* hour 0 on a 12-hour clock is written as 12 */
615
            if (hh == 0)
616
                hh = 12;
617
        }
618
619
        /* run through the format and build the result string */
620
        for (ret = '', local i = 1, local len = fmt.length() ; i <= len ; ++i)
621
        {
622
            /* check what we have */
623
            match = rexMatch(
624
                '<case>h|hh|m|mm|a|A|am|AM|a%.m%.|A.%M%.|24|%%', fmt, i);
625
            if (match == nil)
626
            {
627
                /* no match - copy this character literally */
628
                ret += fmt.substr(i, 1);
629
            }
630
            else
631
            {
632
                /* we have a match - check what we have */
633
                switch (fmt.substr(i, match))
634
                {
635
                case 'h':
636
                    /* add the hour, with no leading zero */
637
                    ret += toString(hh);
638
                    break;
639
640
                case 'hh':
641
                    /* add the hour, with a leading zero if needed */
642
                    if (hh < 10)
643
                        ret += '0';
644
                    ret += toString(hh);
645
                    break;
646
647
                case 'm':
648
                    /* add the minute, with no leading zero */
649
                    ret += toString(mm);
650
                    break;
651
                    
652
                case 'mm':
653
                    /* add the minute, with a leading zero if needed */
654
                    if (mm < 10)
655
                        ret += '0';
656
                    ret += toString(mm);
657
                    break;
658
                    
659
                case 'a':
660
                    /* add the am/pm indicator */
661
                    ret += (pm ? pmStr : amStr);
662
                    break;
663
                    
664
                case '%':
665
                    /* add the next character literally */
666
                    ++i;
667
                    ret += fmt.substr(i, 1);
668
                    break;
669
                }
670
671
                /* skip any extra characters in the field */
672
                i += match - 1;
673
            }
674
        }
675
676
        /* return the result string */
677
        return ret;
678
    }
679
680
    /* pre-initialize */
681
    execute()
682
    {
683
        local vec;
684
        
685
        /* build a list of all of the ClockEvent objects in the game */
686
        vec = new Vector(10);
687
        forEachInstance(ClockEvent, {x: vec.append(x)});
688
689
        /* sort the list by time */
690
        vec.sort(SortAsc, {a, b: a.compareTime(b)});
691
692
        /* store it */
693
        eventList = vec.toList();
694
695
        /* 
696
         *   The earliest event is always the marker for the beginning of
697
         *   the game.  Since it's now the start of the game, mark the
698
         *   first event in our list as reached.  (The first event is
699
         *   always the earliest we find, by virtue of the sort we just
700
         *   did.)  
701
         */
702
        vec[1].eventReached();
703
    }
704
705
    /* 
706
     *   Receive notification from a clock event that an event has just
707
     *   occurred.  (This isn't normally called directly from game code;
708
     *   instead, game code should usually call the ClockEvent object's
709
     *   eventReached() method.)  
710
     */
711
    eventReached(evt)
712
    {
713
        local idx;
714
        
715
        /* find the event in our list */
716
        idx = eventList.indexOf(evt);
717
718
        /* 
719
         *   Never go backwards - if events fire out of order, keep only
720
         *   the later event.  (Games should generally be constructed in
721
         *   such a way that events can only fire in order to start with,
722
         *   but in case a weird case slips through, we make this extra
723
         *   test to ensure that the player doesn't see any strange
724
         *   retrograde motion on the clock.) 
725
         */
726
        if (lastEvent != nil && lastEvent.compareTime(evt) > 0)
727
            return;
728
        
729
        /* note the current time */
730
        curTime = evt.eventTime;
731
732
        /* if there's another event following, note the next time */
733
        if (idx < eventList.length())
734
            nextTime = eventList[idx + 1].eventTime;
735
        else
736
            nextTime = nil;
737
738
        /* 
739
         *   we're committing to an exact wall-clock time, so remember the
740
         *   current turn counter as the last commit point 
741
         */
742
        turnLastCommitted = Schedulable.gameClockTime;
743
    }
744
745
    /* add minutes to a [dd,hh,mm] value, returning a new [dd,hh,mm] value */
746
    addMinutes(t, mm)
747
    {
748
        /* add the minutes; if that takes us over 60, carry to hours */
749
        if ((t[3] += mm) >= 60)
750
        {
751
            local hh;
752
            
753
            /* we've passed 60 minutes - figure how many hours that is */
754
            hh = t[3] / 60;
755
756
            /* keep only the excess-60 minutes in the minutes slot */
757
            t[3] %= 60;
758
759
            /* add the hours; if that takes us over 24, carry to days */
760
            if ((t[2] += hh) >= 24)
761
            {
762
                local dd;
763
                
764
                /* we've passed 24 hours - figure how many days that is */
765
                dd = t[2] / 24;
766
767
                /* keep only the excess-24 hours in the hours slot */
768
                t[2] %= 24;
769
770
                /* add the days */
771
                t[1] += dd;
772
            }
773
        }
774
775
        /* return the adjusted time */
776
        return t;
777
    }
778
779
    /* get the difference in minutes between two [dd,hh,mm] values */
780
    diffMinutes(t1, t2)
781
    {
782
        local mm;
783
        local hh;
784
        local dd;
785
        local bhh = 0;
786
        local bdd = 0;
787
        
788
        /* get the difference in minutes; if negative, note the borrow */
789
        mm = t1[3] - t2[3];
790
        if (mm < 0)
791
        {
792
            mm += 60;
793
            bhh = 1;
794
        }
795
796
        /* get the difference in hours; if negative, note the borrow */
797
        hh = t1[2] - t2[2] - bhh;
798
        if (hh < 0)
799
        {
800
            hh += 24;
801
            bdd = 1;
802
        }
803
804
        /* get the difference in days */
805
        dd = t1[1] - t2[1] - bdd;
806
807
        /* add them all together to get the total minutes */
808
        return mm + 60*hh + 60*24*dd;
809
    }
810
811
    /* 
812
     *   our list of clock events (we build this automatically during
813
     *   pre-initialization) 
814
     */
815
    eventList = nil
816
817
    /* the current game clock time */
818
    curTime = nil
819
820
    /* the most recent event that we reached */
821
    lastEvent = nil
822
823
    /* the next event's game clock time */
824
    nextTime = nil
825
826
    /* 
827
     *   The turn counter (Schedulable.gameClockTime) on the last turn
828
     *   where committed to a specific time.  Each time we check the time,
829
     *   we look here to see how many turns have elapsed since the last
830
     *   time check, and we use this to choose a plausible scale for the
831
     *   wall-clock time change.  
832
     */
833
    turnLastCommitted = 0
834
;
835
836
/*
837
 *   Clock-setting plot event.  This object represents a plot point that
838
 *   occurs at a particular time in the story world.  Create one of these
839
 *   for each of your plot events.  The Clock Manager automatically builds
840
 *   a list of all of these objects during pre-initialization, so you don't
841
 *   have to explicitly tell the clock manager about these.
842
 *   
843
 *   Whenever the story reaches one of these events, you should call the
844
 *   eventReached() method of the event object.  This will set the clock
845
 *   time to the event's current time, and take note of how long we have
846
 *   until the next plot event.  
847
 */
848
class ClockEvent: object
849
    /*
850
     *   The time at which this event occurs.  This is expressed as a list
851
     *   with three elements: the day number, the hour (on a 24-hour
852
     *   clock), and the minute.  The day number is relative to the start
853
     *   of the game - day 1 is the first day of the game.  So, for
854
     *   example, to express 2:40pm on the second day of the game, you'd
855
     *   write [2,14,40].  Note that 12 AM is written as 0 (zero) on a
856
     *   24-hour clock, so 12:05am on day 1 would be [1,0,5].  
857
     */
858
    eventTime = [1,0,0]
859
860
    /* get a formatted version of the event time */
861
    formatTime(fmt) { return clockManager.formatTime(eventTime, fmt); }
862
863
    /* 
864
     *   Compare our event time to another event's time.  Returns -1 if our
865
     *   time is earlier than other's, 0 if we're equal, and 1 if we're
866
     *   after other. 
867
     */
868
    compareTime(other)
869
    {
870
        local a = eventTime;
871
        local b = other.eventTime;
872
        
873
        /* compare based on the most significant element that differs */
874
        if (a[1] != b[1])
875
            return a[1] - b[1];
876
        else if (a[2] != b[2])
877
            return a[2] - b[2];
878
        else
879
            return a[3] - b[3];
880
    }
881
882
    /*
883
     *   Notify the clock manager that this event has just occurred.  This
884
     *   sets the game clock to the event's time.  The game code must call
885
     *   this method when our point in the plot is reached.  
886
     */
887
    eventReached()
888
    {
889
        /* notify the clock manager */
890
        clockManager.eventReached(self);
891
    }
892
;
893
894
/* ------------------------------------------------------------------------ */
895
/*
896
 *   [Footnote 1]
897
 *   
898
 *   "Schrodinger's cat" is a famous thought experiment in quantum
899
 *   physics, concerning how a quantum mechanical system exists in
900
 *   multiple, mutually exclusive quantum states simultaneously until an
901
 *   observer forces the system to assume only one of the states by the
902
 *   act of observation.  The thought experiment has been popularized as
903
 *   an illustration of how weird and wacky QM is, but it's interesting to
904
 *   note that Schrodinger actually devised it to expose what he saw as an
905
 *   unacceptable paradox in quantum theory.
906
 *   
907
 *   The thought experiment goes like this: a cat is locked inside a
908
 *   special box that's impervious to light, X-rays, etc., so that no one
909
 *   on the outside can see what's going on inside.  The box contains,
910
 *   apart from the cat, a little radiation source and a radiation
911
 *   counter.  When the counter detects a certain radioactive emission, it
912
 *   releases some poison gas, killing the cat.  The radioactive emission
913
 *   is an inherently quantum mechanical, unpredictable process, and as
914
 *   such can (and must) be in a superposition of "emitted" and "not
915
 *   emitted" states until observed.  Because the whole system is
916
 *   unobservable from the outside, the supposition is that everything
917
 *   inside is "entangled" with the quantum state of the radioactive
918
 *   emission, hence the cat is simultaneously living and dead until
919
 *   someone opens the box and checks.  It's not just that no one knows;
920
 *   rather, the cat is actually and literally alive and dead at the same
921
 *   time.
922
 *   
923
 *   Schrodinger's point was that this superposition of the cat's states
924
 *   is a necessary consequence of the way QM was interpreted at the time
925
 *   he devised the experiment, but that it's manifestly untrue, since we
926
 *   know that cats are macroscopic objects that behave according to
927
 *   classical, deterministic physics.  Hence a paradox, hence the
928
 *   interpretation of the theory must be wrong.  The predominant
929
 *   interpretation of QM has since shifted a bit so that the cat would
930
 *   now count as an observer - not because it's alive or conscious or
931
 *   anything metaphysical, but simply because it's macroscopic - so the
932
 *   cat's fate is never actually entangled with the radioactive source's
933
 *   quantum state.  Popular science writers have continued to talk about
934
 *   Schrodinger's cat as though it's for real, maybe to make QM seem more
935
 *   exotic to laypersons, but most physicists today wouldn't consider the
936
 *   experiment to be possible as literally stated.  Physicists today
937
 *   might think of it as a valid metaphor to decribe systems where all of
938
 *   the components are on an atomic or subatomic scale, but no one today
939
 *   seriously thinks you can create an actual cat that's simultaneously
940
 *   alive and dead.  
941
 */
942