forked from EricEve/adv3lite
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy patheventList.t
714 lines (638 loc) · 25.4 KB
/
eventList.t
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
#charset "us-ascii"
#include "advlite.h"
/*
* ************************************************************************
* eventList.t This module forms an optional part of the adv3Lite library.
*
*
* (c) 2012-13 Eric Eve (but based largely on code borrowed from the adv3
* library Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved.
* Lightly adapted by Eric Eve for use in the advLite library
*
* adv3Lite Library - EventLists
*
* This module contains definitions of various types of EventLists. These are
* defined in a separate module so that games that don't require EventLists
* can exclude this module from the build. The Script class, from which all
* EventLists inherits is defined in misc.t to allow other modules to test for
* an object being ofKind(Script) even when this EventList module is not
* present.
*/
/*
* Random-Firing script add-in. This is a mix-in class that you can add
* to the superclass list of any Script subclass to make the script
* execute only a given percentage of the time it's invoked. Each time
* doScript() is invoked on the script, we'll look at the probability
* settings (see the properties below) to determine whether we really
* want to execute the script this time; if so, we'll proceed with the
* scripted event, otherwise we'll just return immediately, doing
* nothing.
*
* Note that this must be used in the superclass list *before* the Script
* subclass:
*
* myScript: RandomFiringScript, EventList
*. // ...my definitions...
*. ;
*
* This class is especially useful for random atmospheric events, because
* it allows you to make the timing of scripted events random. Rather
* than making a scripted event happen on every single turn, you can use
* this to make events happen only sporadically. It can often feel too
* predictable and repetitious when a random background event happens on
* every single turn; firing events less frequently often makes them feel
* more realistic.
*/
class RandomFiringScript: object
/*
* Percentage of the time an event occurs. By default, we execute an
* event 100% of the time - meaning every time that doScript() is
* invoked. If you set this to a lower percentage, then each time
* doScript() is invoked, we'll randomly decide whether or not to
* execute an event based on this percentage. For example, if you
* want an event to execute on average about a third of the time, set
* this to 33.
*
* Note that this is a probabilistic frequency. Setting this to 33
* does *not* mean that we'll execute exactly every third time.
* Rather, it means that we'll randomly execute or not on each
* invocation, and averaged over a large number of invocations, we'll
* execute about a third of the time.
*/
eventPercent = 100
/*
* Random atmospheric events can get repetitive after a while, so we
* provide an easy way to reduce the frequency of our events after a
* while. This way, we'll generate the events more frequently at
* first, but once the player has seen them enough to get the idea,
* we'll cut back. Sometimes, the player will spend a lot of time in
* one place trying to solve a puzzle, so the same set of random
* events can get stale. Set eventReduceAfter to the number of times
* you want the events to be generated at full frequency; after we've
* fired events that many times, we'll change eventPercent to
* eventReduceTo. If eventReduceAfter is nil, we won't ever change
* eventPercent.
*/
eventReduceAfter = nil
eventReduceTo = nil
/*
* When doScript() is invoked, check the event probabilities before
* proceeding.
*/
doScript()
{
/* process the script step only if the event odds allow it */
if (checkEventOdds())
inherited();
}
/*
* Check the event odds to see if we want to fire an event at all on
* this invocation.
*/
checkEventOdds()
{
/*
* check the event odds to see if we fire an event this time; if
* not, we're done with the script invocation
*/
if (rand(100) >= eventPercent)
return nil;
/*
* we're firing an event this time, so count this against the
* reduction limit, if there is one
*/
if (eventReduceAfter != nil)
{
/* decrement the limit counter */
--eventReduceAfter;
/* if it has reached zero, apply the reduced frequency */
if (eventReduceAfter == 0)
{
/* apply the reduced frequency */
eventPercent = eventReduceTo;
/* we no longer have a limit to look for */
eventReduceAfter = nil;
}
}
/* indicate that we do want to fire an event */
return true;
}
;
/* ------------------------------------------------------------------------ */
/*
* An "event list." This is a general-purpose type of script that lets
* you define the scripted events separately from the Script object.
*
* The script is driven by a list of values; each value represents one
* step of the script. Each value can be a single-quoted string, in
* which case the string is simply displayed; a function pointer, in
* which case the function is invoked without arguments; another Script
* object, in which case the object's doScript() method is invoked; a
* property pointer, in which case the property of 'self' (the EventList
* object) is invoked with no arguments; or nil, in which case nothing
* happens.
*
* This base type of event list runs through the list once, in order, and
* then simply stops doing anything once we pass the last event.
*/
class EventList: Script
construct(lst) { eventList = lst; }
/* the list of events */
eventList = []
/* cached length of the event list */
eventListLen = (eventList.length())
/* advance to the next state */
advanceState()
{
/* increment our state index */
++curScriptState;
}
/* by default, start at the first list element */
curScriptState = 1
/* process the next step of the script */
doScript()
{
/* get our current event state */
local idx = getScriptState();
/* get the list (evaluate it once to avoid repeated side effects) */
local lst = eventList;
/* cache the length */
eventListLen = lst.length();
/* if it's a valid index in our list, fire the event */
if (idx >= 1 && idx <= eventListLen)
{
/* carry out the event */
doScriptEvent(lst[idx]);
}
/* perform any end-of-script processing */
scriptDone();
}
/* carry out one script event */
doScriptEvent(evt)
{
/* check what kind of event we have */
switch (dataTypeXlat(evt))
{
case TypeSString:
/* it's a string - display it */
say(evt);
break;
case TypeObject:
/* it must be a Script object - invoke its doScript() method */
evt.doScript();
break;
case TypeFuncPtr:
/* it's a function pointer - invoke it */
(evt)();
break;
case TypeProp:
/* it's a property of self - invoke it */
self.(evt)();
break;
default:
/* do nothing in other cases */
break;
}
}
/*
* Perform any end-of-script processing. By default, we advance the
* script to the next state.
*
* Some scripts might want to override this. For example, a script
* could be driven entirely by some external timing; the state of a
* script could vary once per turn, for example, or could change each
* time an actor pushes a button. In these cases, invoking the
* script wouldn't affect the state of the event list, so the
* subclass would override scriptDone() so that it does nothing at
* all.
*/
scriptDone()
{
/* advance to the next state */
advanceState();
}
;
/*
* An "external" event list is one whose state is driven externally to
* the script. Specifically, the state is *not* advanced by invoking the
* script; the state is advanced exclusively by some external process
* (for example, by a daemon that invokes the event list's advanceState()
* method).
*/
class ExternalEventList: EventList
scriptDone() { }
;
/*
* A cyclical event list - this runs through the event list in order,
* returning to the first element when we pass the last element.
*/
class CyclicEventList: EventList
advanceState()
{
/* go to the next state */
++curScriptState;
/* if we've passed the end of the list, loop back to the start */
if (curScriptState > eventListLen)
curScriptState = 1;
}
;
/*
* A stopping event list - this runs through the event list in order,
* then stops at the last item and repeats it each time the script is
* subsequently invoked.
*
* This is often useful for things like ASK ABOUT topics, where we reveal
* more information when asked repeatedly about a topic, but eventually
* reach a point where we've said everything:
*
*. >ask bob about black book
*. "What makes you think I know anything about it?" he says, his
* voice shaking.
*
* >again
*. "No! You can't make me tell you!"
*
* >again
*. "All right, I'll tell you what you want to know! But I warn you,
* these are things mortal men were never meant to know. Your life, your
* very soul will be in danger from the moment you hear these dark secrets!"
*
* >again
*. [scene missing]
*
* >again
*. "I've already told you all I know."
*
* >again
*. "I've already told you all I know."
*/
class StopEventList: EventList
advanceState()
{
/* if we haven't yet reached the last state, go to the next one */
if (curScriptState < eventListLen)
++curScriptState;
}
;
/*
* A synchronized event list. This is an event list that takes its
* actions from a separate event list object. We get our current state
* from the other list, and advancing our state advances the other list's
* state in lock step. Set 'masterObject' to refer to the master list
* whose state we synchronize with.
*
* This can be useful, for example, when we have messages that reflect
* two different points of view on the same events: the messages for each
* point of view can be kept in a separate list, but the one list can be
* a slave of the other to ensure that the two lists are based on a
* common state.
*/
class SyncEventList: EventList
/* my master event list object */
masterObject = nil
/* my state is simply the master list's state */
getScriptState() { return masterObject.getScriptState(); }
/* to advance my state, advance the master list's state */
advanceState() { masterObject.advanceState(); }
/* let the master list take care of finishing a script step */
scriptDone() { masterObject.scriptDone(); }
;
/*
* Randomized event list. This is similar to a regular event list, but
* chooses an event at random each time it's invoked.
*/
class RandomEventList: RandomFiringScript, EventList
/* process the next step of the script */
doScript()
{
/* check the odds to see if we want to fire an event at all */
if (!checkEventOdds())
return;
/* get our next random number */
local idx = getNextRandom();
/* cache the list and its length, to avoid repeated side effects */
local lst = eventList;
eventListLen = lst.length();
/* run the event, if the index is valid */
if (idx >= 1 && idx <= eventListLen)
doScriptEvent(lst[idx]);
}
/*
* Get the next random state. By default, we simply return a number
* from 1 to the number of entries in our event list. This is a
* separate method to allow subclasses to customize the way the
* random number is selected.
*/
getNextRandom()
{
/*
* Note that rand(n) returns a number from 0 to n-1 inclusive;
* since list indices run from 1 to list.length, add one to the
* result of rand(list.length) to get a value in the proper range
* for a list index.
*/
return rand(eventListLen) + 1;
}
;
/*
* Shuffled event list. This is similar to a random event list, except
* that we fire our events in a "shuffled" order rather than an
* independently random order. "Shuffled order" means that we fire the
* events in random order, but we don't re-fire an event until we've run
* through all of the other events. The effect is as though we were
* dealing from a deck of cards.
*
* For the first time through the main list, we normally shuffle the
* strings immediately at startup, but this is optional. If shuffleFirst
* is set to nil, we will NOT shuffle the list the first time through -
* we'll run through it once in the given order, then shuffle for the
* next time through, then shuffle again for the next, and so on. So, if
* you want a specific order for the first time through, just define the
* list in the desired order and set shuffleFirst to nil.
*
* You can optionally specify a separate list of one-time-only sequential
* strings in the property firstEvents. We'll run through these strings
* once. When we've exhausted them, we'll switch to the main eventList
* list, showing it one time through in its given order, then shuffling
* it and running through it again, and so on. The firstEvents list is
* never shuffled - it's always shown in exactly the order given.
*/
class ShuffledEventList: RandomFiringScript, EventList
/*
* a list of events to go through sequentially, in the exact order
* specified, before firing any events from the main list
*/
firstEvents = []
/*
* Flag: shuffle the eventList list before we show it for the first
* time. By default, this is set to true, so that the behavior is
* random on each independent run of the game. However, it might be
* desirable in some cases to always use the original ordering of the
* eventList list the first time through the list. If this is set to
* nil, we won't shuffle the list the first time through.
*/
shuffleFirst = true
/*
* Flag: suppress repeats in the shuffle. If this is true, it
* prevents a given event from showing up twice in a row, which could
* otherwise happen right after a shuffle. This is ignored for lists
* with one or two events: it's impossible to prevent repeats in a
* one-element list, and doing so in a two-element list would produce
* a predictable A-B-A-B... pattern.
*
* You might want to set this to nil for lists of three or four
* elements, since such short lists can result in fairly
* un-random-looking sequences when repeats are suppressed, because
* the available number of permutations drops significantly.
*/
suppressRepeats = true
/* process the next step of the script */
doScript()
{
/* cache the lists to avoid repeated side effects */
local firstLst = firstEvents;
local firstLen = firstLst.length();
local lst = eventList;
eventListLen = lst.length();
/* process the script step only if the event odds allow it */
if (!checkEventOdds())
return;
/*
* States 1..N, where N is the number of elements in the
* firstEvents list, simply show the firstEvents elements in
* order.
*
* If we're set to shuffle the main eventList list initially, all
* states above N simply show elements from the eventList list in
* shuffled order.
*
* If we're NOT set to shuffle the main eventList list initially,
* the following apply:
*
* States N+1..N+M, where M is the number of elements in the
* eventList list, show the eventList elements in order.
*
* States above N+M show elements from the eventList list in
* shuffled order.
*/
local evt;
if (curScriptState <= firstLen)
{
/* simply fetch the next string from firstEvents */
evt = firstEvents[curScriptState++];
}
else if (!shuffleFirst && curScriptState <= firstLen + eventListLen)
{
/* fetch the next string from eventList */
evt = lst[curScriptState++ - firstLen];
}
else
{
/* we're showing shuffled strings from the eventList list */
evt = lst[getNextRandom()];
}
/* execute the event */
doScriptEvent(evt);
}
/*
* Get the next random event. We'll pick an event from our list of
* events using a ShuffledIntegerList to ensure we pick each value
* once before re-using any values.
*/
getNextRandom()
{
/* if we haven't created our shuffled list yet, do so now */
if (shuffledList_ == nil)
{
/*
* create a shuffled integer list - we'll use these shuffled
* integers as indices into our event list
*/
shuffledList_ = new ShuffledIntegerList(1, eventListLen);
/* apply our suppressRepeats option to the shuffled list */
shuffledList_.suppressRepeats = suppressRepeats;
}
/* ask the shuffled list to pick an element */
return shuffledList_.getNextValue();
}
/* our ShuffledList - we'll initialize this on demand */
shuffledList_ = nil
;
/* ------------------------------------------------------------------------ */
/*
* Shuffled List - this class keeps a list of values that can be returned
* in random order, but with the constraint that we never repeat a value
* until we've handed out every value. Think of a shuffled deck of
* cards: the order of the cards handed out is random, but once a card is
* dealt, it can't be dealt again until we put everything back into the
* deck and reshuffle.
*/
class ShuffledList: object
/*
* the list of values we want to shuffle - initialize this in each
* instance to the set of values we want to return in random order
*/
valueList = []
/*
* Flag: suppress repeated values. We mostly suppress repeats by our
* very design, since we run through the entire list before repeating
* anything in the list. However, there's one situation (in a list
* with more than one element) where a repeat can occur: immediately
* after a shuffle, we could select the last element from the
* previous shuffle as the first element of the new shuffle. If this
* flag is set, we'll suppress this type of repeat by choosing again
* any time we're about to choose a repeat.
*
* Note that we ignore this for a list of one element, since it's
* obviously impossible to avoid repeats in this case. We also
* ignore it for a two-element list, since this would produce the
* predictable pattern A-B-A-B..., defeating the purpose of the
* shuffle.
*/
suppressRepeats = nil
/* create from a given list */
construct(lst)
{
/* remember our list of values */
valueList = lst;
}
/*
* Get a random value. This will return a randomly-selected element
* from 'valueList', but we'll return every element of 'valueList'
* once before repeating any element.
*
* If we've returned every value on the current round, we'll
* automatically shuffle the values and start a new round.
*/
getNextValue()
{
local i;
local ret;
local justReshuffled = nil;
/* if we haven't initialized our vector, do so now */
if (valuesVec == nil)
{
/* create the vector */
valuesVec = new Vector(valueList.length(), valueList);
/* all values are initially available */
valuesAvail = valuesVec.length();
}
/* if we've exhausted our values on this round, start over */
if (valuesAvail == 0)
{
/* shuffle the elements */
reshuffle();
/* note that we just did a shuffle */
justReshuffled = true;
}
/* pick a random element from the 'available' partition */
i = rand(valuesAvail) + 1;
/*
* If we just reshuffled, and we're configured to suppress a
* repeat immediately after a reshuffle, and we chose the first
* element of the vector, and we have at least three elements,
* choose a different element. The first element in the vector is
* always the last element we return from each run-through, since
* the 'available' partition is at the start of the list and thus
* shrinks down until it contains only the first element.
*
* If we have one element, there's obviously no point in trying to
* suppress repeats. If we have two elements, we *still* don't
* want to suppress repeats, because in this case we'd generate a
* predicatable A-B-A-B pattern (because we could never have two
* A's or two B's in a row).
*/
if (justReshuffled && suppressRepeats && valuesAvail > 2)
{
/*
* we don't want repeats, so choose anything besides the
* first element; keep choosing until we get another element
*/
while (i == 1)
i = rand(valuesAvail) + 1;
}
/* remember the element we're returning */
ret = valuesVec[i];
/*
* Move the value at the top of the 'available' partition down
* into the hole we're creating at 'i', since we're about to
* reduce the size of the 'available' partition to reflect the
* use of one more value; that would leave the element at the top
* of the partition homeless, so we need somewhere to put it.
* Luckily, we also need to delete element 'i', since we're using
* this element. Solve both problems at once by moving element
* we're rendering homeless into the hole we're creating.
*/
valuesVec[i] = valuesVec[valuesAvail];
/* move the value we're returning into the top slot */
valuesVec[valuesAvail] = ret;
/* reduce the 'available' partition by one */
--valuesAvail;
/* return the result */
return ret;
}
/*
* Shuffle the values. This puts all of the values back into the
* deck (as it were) for a new round. It's never required to call
* this, because getNextValue() automatically shuffles the deck and
* starts over each time it runs through the entire deck. This is
* provided in case the caller has a reason to want to put all the
* values back into play immediately, before every value has been
* dealt on the current round.
*/
reshuffle()
{
/*
* Simply reset the counter of available values. Go with the
* original source list's length, in case we haven't initialized
* our internal vector yet.
*/
valuesAvail = valueList.length();
}
/*
* Internal vector of available/used values. Elements from 1 to
* 'valuesAvail', inclusive, are still available for use on this
* round. Elements above 'valuesAvail' have already been used.
*/
valuesVec = nil
/* number of values still available on this round */
valuesAvail = 0
;
/*
* A Shuffled Integer List is a special kind of Shuffled List that
* returns integers in a given range. Like an ordinary Shuffled List,
* we'll return integers in the given range in random order, but we'll
* only return each integer once during a given round; when we exhaust
* the supply, we'll reshuffle the set of integers and start over.
*/
class ShuffledIntegerList: ShuffledList
/*
* The minimum and maximum values for our range. Instances should
* define these to the range desired.
*/
rangeMin = 1
rangeMax = 10
/* initialize the value list on demand */
valueList = nil
/* construct with the given range */
construct(rmin, rmax)
{
rangeMin = rmin;
rangeMax = rmax;
}
/* get the next value */
getNextValue()
{
/*
* If we haven't set up our value list yet, do so now. This is
* simply a list of integers from rangeMin to rangeMax.
*/
if (valueList == nil)
{
local ele = rangeMin;
valueList = List.generate({i: ele++}, rangeMax - rangeMin + 1);
}
/* use the inherited handling to select from our value list */
return inherited();
}
;