-
Notifications
You must be signed in to change notification settings - Fork 11
/
messages.t
1185 lines (1025 loc) · 40.5 KB
/
messages.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
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#charset "us-ascii"
#include "advlite.h"
/*
* ***************************************************************************
* messages.t
*
* This module forms part of the adv3Lite library (c) 2012-13 Eric Eve, but is
* based substantially on the Mercury Library (c) 2012 Michael J. Roberts
*/
/* ------------------------------------------------------------------------ */
/*
* Global narration parameters. This object's properties control the way
* parameter-based messages are generated. The message generator
* consults the narration parameters each time it generates a message, so
* you can change the settings on the fly, and subsequent output will
* automatically adapt to the latest settings.
*/
Narrator: object
/*
* The verb tense of the narration. This is one of the VerbTense
* objects (Present, Past, Perfect, Past Perfect, Future, Future
* Perfect). This controls the way {verb} substitution parameters
* are generated, which in turn affects most library messages. The
* default is Present, which is the conventional tense for most IF.
*
* Examples of the English tenses:
*
*. Present: Bob opens the box.
*. Past: Bob opened the box.
*. Perfect: Bob has opened the box.
*. Past Perfect: Bob had opened the box.
*. Future: Bob will open the box.
*. Future Perfect: Bob will have opened the box.
*
* (Language modules are free to add their own tenses if the target
* language has others that would be of interest to IF authors.
* They're also free to ignore any of the "standard" tenses we
* define. At a minimum, though, some form of present tense should
* always be provided, since most IF is narrated in the present. If
* you need to differentiate among different present tenses, you
* might prefer to define your own IDs instead of using the generic
* Present, but you should still support *some* present tense that's
* suitable for narration. Some type of past tense usable in
* narration is also nice to have. The others are probably of
* marginal value; in English, at least, other tenses are rare in any
* kind of narrative fiction, and are mostly limited to experimental
* or novelty use.)
*/
tense = (gameMain.usePastTense ? Past : Present)
;
/*
* Root class for verb tenses
*/
class VerbTense: object
/*
* Tense name. This is just an identifier for internal and degugging
* purposes, so we don't bother farming it out to the language module
* for translation.
*/
name = nil
;
Present: VerbTense
name = 'present'
;
Past: VerbTense
name = 'past'
;
Perfect: VerbTense
name = 'perfect'
;
PastPerfect: VerbTense
name = 'past perfect'
;
Future: VerbTense
name = 'future'
;
FuturePerfect: VerbTense
name = 'future perfect'
;
/* ------------------------------------------------------------------------ */
/*
* Show a message.
*
* This looks for a customized version of the message text, as defined in
* CustomMessages objects. If we don't find one, we use the provided default
* message text.
*
* Substitution parameters take the form {x y z} - curly braces with one or
* more space-delimited tokens. The first token is the parameter name, and
* any additional tokens are arguments. The parameter names and their
* arguments are up to the language module to define.
*
* In addition to the parameters, the string itself can have two sections,
* separated by a vertical bar, '|'. The first section (before the bar) is
* the "terse" string, which is for a straightforward acknowledgment of a
* simple, ordinary action: "Taken", "Dropped", etc. The terse string is used
* only if the Command argument's actor is the player character, AND the
* command doesn't have any disambiguated objects. If these conditions aren't
* met, the second half of the string, the "verbose" version, is used.
*
* Once we have the message text, we perform parameter substitutions.
* Parameters can be provided as strings, which are substituted in literally;
* or as objects, whose names are inserted according to the grammar in the
* template text.
*/
message(id, txt, [args])
{
txt = buildMessage(id, txt, args...);
/*
* use the oSay function to output the text to avoid an infinite
* recursive call through say.
*/
oSay(txt);
}
/*
* Build a message to be shown by message()
*
* We put this in a separate function to make it easy to obtain the text of a
* message for subsequent use without first displaying it.
*/
buildMessage(id, txt, [args])
{
/* look for a customized version of the message */
local cm = nil;
foreach (local c in CustomMessages.all)
{
/*
* if this customizer is active and defines the message, and it
* has a higher priority than any previous customizer we've
* already found, remember it as the best candidate so far
*/
if (c.active && c.msgTab[id] != nil
&& (cm == nil || c.priority > cm.priority))
cm = c;
}
/* show debugging information, if desired */
IfDebug(messages, debugMessage(id, txt, cm, args));
/* if we found an override, use it instead of the provided text */
if (cm != nil)
txt = cm.msgTab[id];
/* if txt is a function pointer, retrieve the value it returns */
if(dataType(txt) == TypeFuncPtr)
txt = txt();
/* set up a sentence context for the expansion */
local ctx = new MessageCtx(args);
/*
* Carry out any language-specific adjustments to the text prior to any
* further processing
*/
txt = langAdjust(txt);
/*
* First look for a tense-swithing message substitution of the form
* {present-string|past-string} and choose whichever is appropriate to the
* tense of the game.
*/
local bar, openBrace = 0, closeBrace = 0, newTxt = txt;
for(;;)
{
/* Find the next opening brace */
openBrace = txt.find('{', closeBrace + 1);
/* If there isn't one, we're done, so leave the loop. */
if(openBrace == nil)
break;
/* Find the next vertical bar that follows the opening brace */
bar = txt.find('|', openBrace);
/* If there isn't one, we're done, so leave the loop. */
if(bar == nil)
break;
/* Find the next closing brace that follows the opening brace */
closeBrace = txt.find('}', openBrace);
/* If there isn't one, we're done, so leave the loop. */
if(closeBrace == nil)
break;
/*
* If the bar doesn't come before the closing brace, then it's not
* between the two braces, so we don't want to process it in this
* circuit of the loop. Instead we need to see if there's another
* opening brace on the next iteration.
*/
if(bar > closeBrace)
continue;
/*
* Extract the string that starts with the opening brace we found and
* ends with the closing brace we found.
*/
local pString = txt.substr(openBrace, closeBrace - openBrace + 1);
/*
* If the game is in the past tense, extract the second part of this
* above string (that following the bar up to but not including the
* closing brace). Otherwise extract the first part (that following
* but not including the opening brace up to but not including the
* bar)
*/
local subString = (gameMain.usePastTense || Narrator.tense == Past) ?
txt.substr(bar + 1, closeBrace - bar - 1) : txt.substr(openBrace +
1, bar - openBrace - 1);
/*
* In the copy of our original text string, replace the string in
* braces with the substring we just extracted from it.
*/
newTxt = newTxt.findReplace(pString, subString, ReplaceOnce);
}
/* Copy our adjusted string back to our original string */
txt = newTxt;
/* check for separate PC and NPC messages */
bar = txt.find('|');
if (bar != nil)
{
/* there's a bar - check to see if the terse format can be used */
if (ctx.cmd != nil && ctx.cmd.terseOK())
txt = txt.left(bar - 1);
else
txt = txt.substr(bar + 1);
}
/* get the message object */
local mo = MessageParams.langObj;
/* apply substitutions */
for (local i = 1 ; i <= txt.length() ; )
{
/* find the end of the current sentence */
local eos = txt.find(R'<.|!|?><space>', i) ?? txt.length() + 1;
/*
* Build a list of the parameters in the sentence, and preprocess
* each one. We need to preprocess the entire sentence before we
* can expand any of it, because some parameters can depend upon
* other parameters later in the sentence. For example, some
* languages generally place the subject after the verb; to
* generate the verb with proper agreement, we need to know the
* subject when we expand the verb, which means we need to have
* scanned the entire sentence before we expand the verb.
*/
ctx.startSentence();
local plst = new Vector(10);
for (local j = i ; ; )
{
/* find the next parameter */
local lb = txt.find('{', j);
if (lb == nil || lb >= eos)
break;
/* find the end of the parameter */
local rb = txt.find('}', lb + 1);
if (rb == nil)
break;
/* pull out the parameter string */
local param = txt.substr(lb + 1, rb - lb - 1);
/* turn it into a space-delimited token list */
param = param.trim().split(' ');
/*
* do the preliminary expansion, but discard the result -
* this gives the expander a chance to update any effect on
* the sentence context
*/
mo.expand(ctx, param);
/* add it to the list */
plst.append([lb, rb, param]);
/* move past this item */
j = rb + 1;
}
/* restart the sentence */
ctx.endPreScan();
/* do the actual expansion */
local delta = 0;
for (local j = 1 ; j <= plst.length() ; ++j)
{
/* pull out this item */
local cur = plst[j];
local lb = cur[1], rb = cur[2], param = cur[3];
local paramLen = rb - lb + 1;
/* get the expansion */
local sub = mo.expand(ctx, param);
/*
* if it starts with a "backspace" character ('\010', which
* is a ^H character, the standard ASCII backspace), delete
* any spaces immediate preceding the substitution parameter
*/
if (sub.startsWith('\010'))
{
/* count spaces immediately preceding the parameter */
local m = lb + delta - 1, spCnt = 0;
while (m >= 1 && txt.toUnicode(m) == 32)
--m, ++spCnt;
/* if we found any spaces, splice them out */
if (spCnt != 0)
{
/* splice out the spaces */
txt = txt.splice(m + 1, spCnt, '');
/* adjust our delta for the deletion */
delta -= spCnt;
}
/* remove the backspace from the replacement text */
sub = sub.substr(2);
}
/* splice the replacement text into the string */
// txt = txt.splice(lb + delta, paramLen, sub);
txt = txt.substr(1, lb + delta -1) + sub + txt.substr(lb + delta
+ paramLen );
/* adjust our delta to the next item for the splice */
delta += sub.length() - paramLen;
}
/* move to the end of the sentence */
i = eos + delta + 1;
}
return txt;
}
/*
* Message debugging. This shows the message before processing: the ID,
* the default source text with the {...} parameters, the overriding
* custom source text, and the arguments.
*/
#ifdef __DEBUG
debugMessage(id, txt, cm, args)
{
if(id is in (nil,'','command results prefix', 'command prompt', 'command
results suffix')
|| outputManager.curOutputStream != mainOutputStream)
return;
local idchk = [id, libGlobal.totalTurns];
if(DebugCtl.messageIDs[idchk] != nil)
return;
else
DebugCtl.messageIDs[idchk] = true;
oSay('\nmessage(id=<<id>>, default text=\'<<txt>>\' ');
if (cm != nil)
oSay('custom text=\'<<cm.msgTab[id]>>\'');
if (args.length() != 0)
{
oSay(', args={ ');
for (local i = 1 ; i <= args.length() ; ++i)
{
local a = args[i];
if (i > 1)
oSay(', ');
if (dataType(a) == TypeSString)
oSay(''''<<args[i]>>'''');
else
oSay('object(name=<<a.name>>)');
}
oSay(' }');
}
oSay(')\n');
}
#endif
/* ------------------------------------------------------------------------ */
/*
* Noun parameter roles for MessageCtx.noteObj()
*/
/* the noun is the subject of the sentence */
enum vSubject;
/* the noun is an object of the verb */
enum vObject;
/*
* the role is ambiguous (it's not marked clearly in the case or
* position, for example)
*/
enum vAmbig;
/* the noun is a possessive, not the subject or the object */
enum vPossessive;
/* ------------------------------------------------------------------------ */
/*
* Message expansion sentence context. This keeps track of the parts of
* the sentence we've seen so far in the substitution parameters.
*
* The sentence context is important for expanding certain items. For
* verbs, it tells us which object is the subject, so that we can
* generate the agreeing conjugation of the verb (in number and
* grammatical person). For direct and indirect objects, it lets us
* generate a reflexive when the same object appears in a second role
* ("You can't put the box in itself").
*/
class MessageCtx: object
construct(args)
{
/* remember the message arguments */
self.args = args;
/* if there's a Command among the arguments, note it */
cmd = gCommand;
/* if there's no command, use the placeholder */
if (cmd == nil)
cmd = messageDummyCommand;
}
/* start a new sentence */
startSentence()
{
/* forget any subject/object information */
subj = nil;
vobj = nil;
gotVerb = nil;
reflexiveAnte.clear();
/* note that we're on the initial scan */
prescan = true;
/* we're starting a new scan, so reset the previous parameter */
lastParam = nil;
}
/*
* End the pre-expansion scan. The expander makes two passes over
* each sentence. The first scan doesn't actually do any
* substitutions, but merely invokes each parameter to give it a
* chance to exert its side effects on the sentence context. The
* second scan actually applies the substitutions. At the end of the
* first pass, the expander calls this to let us finalize the initial
* scan and prepare for the second scan.
*/
endPreScan()
{
/*
* Forget the reflexive antecedent. Reflexives are generally
* anaphoric (i.e., they refer back to earlier clauses in the
* same sentence), so we generally don't care about anything we
* found later in the same sentence.
*/
reflexiveAnte.clear();
/* we're no longer on the initial scan */
prescan = nil;
/* we're starting a new scan, so reset the previous parameter */
lastParam = nil;
}
/*
* Note a parameter value. Some parameters refer back to the
* immediately preceding parameter, so it's useful to have the most
* recent value stashed away. Returns the parameter value as given.
*/
noteParam(val)
{
/* remember the value */
return lastParam = val;
}
/*
* Convert a parameter value to a string representation suitable for
* message substitution.
*/
paramToString(val)
{
switch (dataType(val))
{
case TypeSString:
return val;
case TypeInt:
return toString(val);
case TypeObject:
if (val.ofKind(Mentionable) || val.ofKind(Pronoun))
return val.name;
else if (val.ofKind(List) || val.ofKind(Vector))
return val.mapAll({ x: paramToString(x) }).join(', ');
else if (val.ofKind(BigNumber))
return toString(val);
else
return '(object)';
case true:
return 'true';
case nil:
return '';
default:
return '(?)';
}
}
/*
* Convert a parameter value to a numeric representation. If the
* value is an integer or BigNumber, we return it as is; if a list or
* vector, we return the number of elements; if nil, 0; if a string,
* the parsed numeric value of the string; otherwise we simply return
* 1.
*/
paramToNum(val)
{
switch (dataType(val))
{
case TypeSString:
return toInteger(val);
case TypeInt:
return val;
case TypeObject:
if (val.ofKind(BigNumber))
return val;
if (val.ofKind(List) || val.ofKind(Vector))
return val.length();
return 1;
case TypeList:
return val.length();
case nil:
return 0;
default:
return 1;
}
}
/*
* Note an object being used as a parameter in the given sentence
* role. The role is one of the noun role enums defined above:
* vSubject, vObject, or vAmbig. If the object is a subject, we'll
* save it as the sentence subject, so that we can generate an
* agreeing verb. Regardless of role, we'll also save it as a
* reflexive antecedent, so that we can generate a reflexive pronoun
* if we see the same object again in another role in the same
* sentence.
*/
noteObj(obj, role)
{
/* remember the object as the last parameter value */
noteParam(obj);
/*
* If the role is ambiguous, guess at the role based on the
* general sentence order.
*/
if (role == vAmbig && MessageParams.langObj.sentenceOrder != nil)
{
/* get the sentence order flags */
local so = MessageParams.langObj.sentenceOrder;
/* figure whether the subject/object is before/after the verb */
local ssv = (so.find(R'S.*V') != nil ? -1 : 1);
local osv = (so.find(R'O.*V') != nil ? -1 : 1);
/* figure whether the subject or object comes first */
local fo = (so.find(R'S.*O') != nil ? vSubject : vObject);
/* figure which side of the verb we're on (-1 before, 1 after) */
local sv = (gotVerb ? 1 : -1);
/*
* If we're on the right side of the verb for both subject
* and object: if we have an object, this is the subject; if
* we have a subject, this is the object; if we have neither
* this is the first role in sentence order.
*
* Otherwise, if we're on the subject side of the verb, and
* we don't have a subject, this is the subject.
*
* Otherwise, if we're on the object side of the verb, and we
* don't have an object, this is an object.
*/
if (ssv == sv && osv == sv)
role = (subj != nil ? vObject :
vobj != nil ? vSubject : fo);
else if (ssv == sv && subj == nil)
role = vSubject;
else if (osv == sv && vobj == nil)
role = vObject;
}
/* if it's the subject, remember it as such */
if (role == vSubject)
subj = obj;
else if (role == vObject)
vobj = obj;
/*
* Only record the reflexive antecent for the subject, so that the
* reflexive pronoun is used when an actor or object tries to act on
* itself.
*/
if(role != vSubject)
return;
/*
* If there's nothing in the reflexive antecedent list that uses
* the same pronoun, add this to the list. Otherwise, replace
* the object that used the same antecedent.
*/
// local p = obj.pronoun();
// local idx = reflexiveAnte.indexWhich({ o: o.pronoun() == p });
/*
* The foregoing rule seems to generate false positives (that is, it
* can result in reflexive pronouns where they're not appropriate), so
* we'll try the different strategy of looking for anything in the
* reflexive ante list. The reason for this is that if a new
* subject is introduced into the sentence, it doesn't have to be the
* same gender as the previous subject to become the most likely
* antecedent for a reflexive.
*/
local idx = reflexiveAnte.indexWhich({ o: o.ofKind(Thing) });
if (idx == nil)
{
/*
* There's no object with this pronoun yet - add it. We want
* to keep earlier antecedents with different pronouns
* because we can still refer back to earlier objects
* reflexively as long as it's clear which one we're talking
* about. The distinct pronouns provide that clarity.
*/
reflexiveAnte.append(obj);
}
else
{
/*
* We already have an object with this pronoun, so replace it
* with the new one. Reflexives generally bind to the prior
* noun phrase that matches the pronoun *and* is closest in
* terms of word order. Once another noun phrase intervenes
* in word order, we can only refer back to an earlier one by
* reflexive pronoun if the earlier noun is distinguishable
* from the later one by its use of a distinct pronoun from
* the later one. For example, we can say "Bob asked Sue
* about himself", because "himself" could only refer to Bob,
* but most people would take "Bob asked Sam about himself"
* to mean that Bob is asking about Sam.
*/
reflexiveAnte[idx] = obj;
}
}
/*
* Note a verb parameter.
*/
noteVerb()
{
/* note that we got a verb */
gotVerb = true;
}
/*
* Was the last parameter value plural? If the value is numeric, 1
* is singular and anything else is plural. If it's a list, a
* one-element list is singular and anything else is plural. If it's
* a Mentionable, the 'plural' property determines it.
*/
lastParamPlural()
{
switch (dataType(lastParam))
{
case TypeInt:
return lastParam != 1;
case TypeObject:
if (lastParam.ofKind(Mentionable) || lastParam.ofKind(Pronoun))
return lastParam.plural;
else if (lastParam.ofKind(List) || lastParam.ofKind(Vector))
return lastParam.length() != 1;
else if (lastParam.ofKind(BigNumber))
return lastParam != 1;
else
return nil;
case TypeList:
return lastParam.length() != 1;
default:
return nil;
}
}
/*
* Is the actor involved in the Command the PC? If there's a Command
* with an actor, we check to see if it's the PC. If there's no
* Command or no actor, we assume that the PC is the relevant actor
* (since there's nothing else specified anywhere) and return true.
*/
actorIsPC()
{
return cmd == nil || cmd.actor == nil
|| cmd.actor == gPlayerChar;
}
/* the last parameter value */
lastParam = nil
/* are we on the initial pre-expansion scan? */
prescan = nil
/* the subject of the sentence (as a Mentionable object) */
subj = nil
/* the last object of the verb we saw */
vobj = nil
/* have we seen a verb parameter in this sentence yet? */
gotVerb = nil
/* the message argument list */
args = nil
/* the Command object among the arguments, if any */
cmd = nil
/*
* The reflexive antecedents. Each time we see an object in a
* non-subject role, and the object has different pronoun usage from
* any previous entry, we'll add it to this list. If we see the same
* object subsequently in another non-subject role, we'll know that
* we should generate a reflexive pronoun for the object rather than
* the name or a regular pronoun:
*
* You can't put the tongs in the box with the tongs -> with
* themselves
*/
reflexiveAnte = perInstance(new Vector(5))
;
/*
* Dummy command placeholder for messages generated without a command.
*/
messageDummyCommand: object
/* use the player character as the actor */
actor = (gPlayerChar)
;
/*
* Use the message builder to format a message without supplying a key
* to look up at alternative message. We can use this with library
* messages that employ object properties (e.g. cannotTakeMsg) or user
* code.
*
* dmsg() displays the resultant message.
*/
dmsg(txt, [args])
{
message('', txt, args...);
}
/* bmsg returns the text of a message formatted by the message formatter. */
bmsg(txt, [args])
{
return buildMessage('', txt, args...);
}
/* ------------------------------------------------------------------------ */
/*
* Message customizer object. Language extensions and games can use this
* class to define their own custom messages that override the default
* English messages used throughout the library.
*
* Each CustomMessages object can define a list of messages to be
* customized. This lets you centrally locate all of your custom
* messages by putting them all in a single object, if you wish.
* Alternatively, you can create separate objects, if you prefer to keep
* them with some other body of code they apply to. In either case, the
* library gathers them all up during preinit.
*/
class CustomMessages: object
/*
* The priority determines the precedence of a message defined in
* this object, if the same message is defined in more than one
* CustomMessages object. The message with the highest priority is
* the one that's actually displayed.
*
* The library defines one standard priority level: 100 is the
* priority for language module overrides. Each language module
* provides a translated set of the standard library messages, via a
* CustomMessages object with priority 100. (The default English
* messages defined in-line throughout the library via DMsg() macros
* effectively have a priority of negative infinity, since any custom
* message of any priority overrides a default.)
*
* Games will generally want to override all library messages,
* including translations, so we set the default here to 200.
*/
priority = 200
/*
* Is this customizer active? If you want to change the messages at
* different points in the course of the game, you can use this to
* turn sets of messages on and off. For example, if your game
* includes narrator changes at certain points, you can create
* separate sets of messages per narrator. By default, we make all
* customizations active, but you can override this to turn selected
* messages on and off as needed. Note that the library consults
* this every time it looks up a message, so you can change the value
* dynamically, or use a method whose return value changes
* dynamically.
*/
active = true
/*
* The message list. This can contain any number of messages; the
* order isn't important. Each message is defined with a Msg()
* macro:
*
*. Msg(id key, 'Message text'), ...
*
* The "id key" is the message ID that the library uses in the DMsg()
* message that you're customizing. (DON'T use quotes around the ID
* key.) The message text is a single-quoted string giving the
* message text. This can contain curly-brace replacement
* parameters.
*/
messages = []
/*
* Construction. Build the lookup table of our messages for fast
* access at run-time.
*/
construct()
{
/* add me to the master list of message customizers */
CustomMessages.all += self;
/* create the lookup table */
msgTab = new LookupTable(64, 128);
/* populate it with our key->string mappings */
for (local i = 1, local len = messages.length() ; i <= len ; i += 2)
msgTab[messages[i]] = messages[i+1];
}
/* message lookup table - this maps ID keys to message text strings */
msgTab = nil
/*
* class property: the master list of CustomMessages objects defined
* throughout the game
*/
all = []
;
/* ------------------------------------------------------------------------ */
/*
* Message Parameter Handler. This object defines and handles parameter
* expansion for '{...}' strings in displayed messages.
*
* The language module must provide one instance of this class. The name
* of the instance doesn't matter - we'll find it at preinit time. The
* object must provide the 'params' property giving the language-specific
* list of substitution parameter names and handler functions.
*/
class MessageParams: object
/*
* Expand a parameter string. 'ctx' is a MessageCtx object with the
* current sentence context. This contains the message expansion
* arguments (ctx.args), the Command object from the arguments
* (ctx.cmd), and information on the grammar elements of the
* sentence. 'params' is the list of space-delimited tokens within
* the curly-brace parameter string. Returns the string to
* substitute for the parameter in the message output.
*/
expand(ctx, params)
{
/* look up the parameter name */
local pname = params[1].trim();
local t = paramTab[pname.toLower()];
/*
* If we found an entry, let the entry handle it. If there's no
* matching entry, it's basically an error: return the original
* parameter source text, with the braces restored, so that it'll
* be obvious in the output.
*/
local txt = nil;
if (t != nil)
{
/*
* If we have not previously identified a subject for this
* context, use the dummy_ object, which provides a dummy third
* person singular noun as a default subject for the verb.
*/
if(ctx.subj == nil)
ctx.subj = dummy_;
/* we found the parameter - get the translation */
txt = t[2](ctx, params);
/*
* if this isn't a pre-scan, and the parameter name starts
* with a capital, mimic that pattern in the result
*/
if (txt != nil && !ctx.prescan && rexMatch(R'<upper>', pname) != nil)
txt = txt.firstChar().toUpper() + txt.delFirst();
}
/*
* if we failed to find an expansion, and this isn't just a
* pre-scan, return the source text as an error indication
*/
if (txt == nil && !ctx.prescan)
txt = '{' + params.join(' ') + '}';
/* return the result */
return txt;
}
/*
* Parameter mapping list. This is a list of lists: [name, func],
* where 'name' is the parameter name (as a string), and 'func' is
* the expansion handler function.
*
* The parameter name must be all lower case. During expansion, we
* convert the first space-delimited token within the {curly brace}
* parameter string to lower case, then look for an entry in the list
* with the matching parameter name. If we find an entry, we call
* its handler function.
*
* The handler function is a pointer to a function that takes two
* arguments: func(params, ctx), where 'params' is the list of tokens
* within the {curly braces} of the substitution string, as a list of
* strings, where each string is a space-delimited token in the
* original {curly brace} string; and 'ctx' is the MessageCtx object
* for the expansion. The function returns a string giving the
* expansion of the parameter.
*
* The parameter list must be provided by the language module, since
* each language's list of parameters and expansions will vary.
*/
params = [ ]
/*
* Some parameters expand to properties of objects involved in the
* command. cmdInfo() makes it easier to define the expansion
* functions for such parameters. We search the parameters for a
* Command object, and if we find it, we retrieve a particular source
* object and evaluate a particular property on the source object to
* get the result string.
*
* For example, {the dobj} could be handled via cmdInfo('ctx, dobj',
* &theName, vSubject): we find the current 'dobj' object in the
* Command, then evaluate the &theName property on that object.
*
* 'ctx' is the MessageCtx object with the current sentence context.
*
* 'src' is the source object in the Command. This can be given as a
* property pointer (&actor, say), in which case we simply evaluate
* that property of the Command object (cmd.(src)) to get the source
* object. Or, it can be a string giving a NounRole name (dobj,
* iobj, acc), in which case we'll retrieve the current object for