FrobTADS is an open source project powered by Assembla

Assembla offers free public and private SVN/Git repositories and project hosting with bug/issue tracking and collaboration tools.

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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
#include <adv3.h>
4
#include <en_us.h>
5
6
  /* 
7
   *   Combine Reports
8
   *
9
   *   by Eric Eve
10
   *
11
   *   Version 1.03 (10-Feb-09)
12
   *
13
   *   This extension is a further development of Example 2 in the Technical 
14
   *   Manual article on Manipulating the Transcript. If this extension is 
15
   *   included in your game, reports of taking, dropping, or putting 
16
   *   (in/on/under/behind) a series of reports will be combined into a 
17
   *   single report listing the objects taken, dropped, or put. Reports of 
18
   *   actions that failed will appear in the normal way, but will be moved 
19
   *   to the end so as not to interrupt the combined report of successful 
20
   *   actions.
21
   *
22
   *   With this extension installed, output like:
23
   *
24
   *.    blue ball: Taken
25
   *.    red pen: Taken
26
   *.    gold coin: Taken
27
   *.    gold coin: Taken
28
   *
29
   *   Is grouped into a single sentence like:
30
   *
31
   *.    You take the blue ball, the red pen, and the two gold coins.
32
   *
33
   *   For PUT actions the extension also groups any implicit take action 
34
   *   reports in the same way. e.g. instead of:
35
   *
36
   *   (first taking the red book, then taking the green ball, then taking 
37
   *   the pen, then taking the gold coin, then taking the gold coin)
38
   *
39
   *   We get:
40
   *
41
   *   (first taking the red book, the green ball, the pen and two gold coins)
42
   *
43
   *   Version History:
44
   *
45
   *   Version 1.03 tidies up the output from a multiple TAKE FROM command
46
   *
47
   *   Version 1.02 makes use of the new standard Library class SimpleLister
48
   *
49
   *   Version 1.01 makes a couple of things more customizable, and corrects 
50
   *   a typo in the report produced for dropping multiple objects.
51
   *
52
   */
53
54
55
56
ModuleID
57
    name = 'combineReports'
58
    byline = 'by Eric Eve'
59
    htmlByline = 'by <a href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>'
60
    version = '1.03'
61
    listingOrder = 75
62
;
63
64
/*  first define a useful lister */
65
66
definiteObjectLister: SimpleLister   
67
        
68
    /* 
69
     *   Use the definite article form of the object ('the ball') unless its 
70
     *   one of several identical objects, in which case the indefinite form 
71
     *   ('a ball') is more appropriate.
72
     */
73
    showListItem(obj, options, pov, infoTab)
74
    {
75
        say(obj.isEquivalent ? obj.aName : obj.theName);
76
    }
77
;
78
79
80
81
/* modify CommandReport to keep a note of the direct object it refers to */
82
83
modify CommandReport
84
    construct()
85
    {
86
        inherited();
87
        dobj_ = gDobj;
88
    }
89
    dobj_ = nil    
90
;
91
92
93
94
/* Make TAKE, DROP and PUT combine reports by using the actionReportManager */
95
96
modify TakeAction
97
    afterActionMain()
98
    {
99
        inherited;
100
        if(parentAction == nil)
101
           actionReportManager.afterActionMain();
102
    }
103
    vCorrect(str) {  return '{take[s]|took}';  }
104
;
105
106
modify TakeFromAction
107
    afterActionMain()
108
    {
109
        inherited;
110
        if(parentAction == nil)
111
           actionReportManager.afterActionMain();
112
    }    
113
    vCorrect(str) {  return '{take[s]|took}';  }
114
;
115
116
modify DropAction
117
    afterActionMain()
118
    {
119
        inherited;
120
        if(parentAction == nil)
121
           actionReportManager.afterActionMain();
122
    }
123
    vCorrect(str) {  return 'drop{s/ped}';  }
124
;
125
126
127
modify PutOnAction
128
    afterActionMain()
129
    {
130
        inherited;
131
        if(parentAction == nil)
132
           actionReportManager.afterActionMain();
133
    }
134
    vCorrect(str) { return '{put[s]|put}'; }
135
;
136
137
modify PutInAction
138
    afterActionMain()
139
    {
140
        inherited;
141
        if(parentAction == nil)
142
           actionReportManager.afterActionMain();
143
    }
144
    vCorrect(str) { return '{put[s]|put}'; }
145
;
146
147
modify PutUnderAction
148
    afterActionMain()
149
    {
150
        inherited;
151
        if(parentAction == nil)
152
           actionReportManager.afterActionMain();
153
    }
154
    vCorrect(str) { return '{put[s]|put}'; }
155
;
156
157
modify PutBehindAction
158
    afterActionMain()
159
    {
160
        inherited;
161
        if(parentAction == nil)
162
           actionReportManager.afterActionMain();
163
    }
164
    vCorrect(str) { return '{put[s]|put}'; }
165
;
166
167
/* 
168
 *   The actionReport Manager does most of the work. It is hopefully 
169
 *   sufficiently general that it could be made to work with other actions 
170
 *   besides TAKE, DROP and PUT IN/ON/UNDER/BEHIND. Experiment at your own 
171
 *   risk!  
172
 */
173
174
actionReportManager: object
175
    afterActionMain()
176
    {        
177
        /* 
178
         *   If the action isn't iterating over at least two direct objects 
179
         *   we have nothing to do, so we'll stop before doing any messing 
180
         *   with the transcript
181
         */
182
        
183
        if(gAction.dobjList_.length() < 2)
184
            return;
185
        
186
          
187
        /* 
188
         *   First move any reports of failed attempts to the end, so they 
189
         *   can be fully reported after the summary of the actions that 
190
         *   succeeded.
191
         */
192
              
193
        
194
        local len = gTranscript.reports_.length;
195
        
196
        for(local i = gTranscript.reports_.indexWhich({x: x.isFailure}); 
197
            i != nil && i <= len; 
198
            i = gTranscript.reports_.indexWhich({x: x.isFailure}))
199
        {               
200
                                   
201
            /* 
202
             *   first find the MultiObjectAnnouncement relating to this 
203
             *   failure
204
             */
205
            
206
            local idx1 = i;
207
            while(idx1 > 1 
208
                  && !gTranscript.reports_[idx1].ofKind(MultiObjectAnnouncement))
209
                idx1--;
210
            
211
            /* then find the next MultiObjectAnnouncement */
212
            local idx2 = i;
213
            while(idx2 <= len 
214
                  && !gTranscript.reports_[idx2].ofKind(MultiObjectAnnouncement))
215
                idx2++;
216
            
217
            /* Extract a list of all reports between these markers */
218
            local objVec = new Vector(20).copyFrom(gTranscript.reports_,
219
                                                   idx1, 1, idx2 - idx1);
220
            
221
            /* 
222
             *   Ensure that all reports about this object are marked as 
223
             *   failures
224
             */
225
            
226
            objVec.forEach({x: x.isFailure = true });
227
            
228
            /* Move this list to the end of the transcript */
229
            gTranscript.reports_.removeRange(idx1, idx2-1);
230
            
231
            gTranscript.reports_.appendAll(objVec);                   
232
            
233
            /* 
234
             *   We don't want to check these reports again, so reduce len by
235
             *   the number of reports we just moved.
236
             */
237
            len -= objVec.length();
238
            
239
        }
240
        
241
        /* 
242
         *   Give the game author the opportunity of further processing the 
243
         *   failure reports, if desired. We also check the summarizeFailures
244
         *   flag (nil by default) so that we don't carry out any pointless 
245
         *   processing if we don't need it here. It also leaves open the 
246
         *   possibility of some future version of this extension defining 
247
         *   its own version of processFailures(), which game authors can 
248
         *   then opt in to using.          
249
         */
250
        if(summarizeFailures && gTranscript.reports_.length > len)
251
        {
252
            local failVec 
253
                = processFailures(new Vector(20).copyFrom(gTranscript.reports_, 
254
                    len + 1, 1, gTranscript.reports_.length() - len));
255
            
256
            gTranscript.reports_ = gTranscript.reports_.setLength(len) +
257
                failVec;
258
        }
259
         /* 
260
         *   Define this function separately as we'll use it more than once; 
261
         *   the function identifies implicit action reports relating to 
262
         *   taking things.
263
         */
264
        
265
        local impFunc = {x: x.ofKind(ImplicitActionAnnouncement) 
266
            && x.action_.ofKind(TakeAction) && !x.isFailure };
267
268
        
269
        /* 
270
         *   Count how many implicit action reports there are relating to 
271
         *   taking things.
272
         */
273
        local impActions = gTranscript.reports_.countWhich(impFunc);
274
                   
275
        /*  We only need to do anything if there's more than one. */
276
        if(impActions > 1)
277
        {
278
            /* Note the location of the first relevant implicit action report */
279
            local firstImp = gTranscript.reports_.indexWhich(impFunc);
280
            
281
            /* Store a copy of this report */
282
            local rep = gTranscript.reports_[firstImp];
283
            
284
            /* Get a list of all the implicit take reports */            
285
            local impVec = gTranscript.reports_.subset(impFunc);
286
            
287
            local impTxt = definiteObjectLister.makeSimpleList(
288
                impVec.mapAll({x: x.dobj_}).getUnique()
289
                );
290
            
291
            /* 
292
             *   Change the text of this implicit action report to account 
293
             *   for all the objects implicitly taken             
294
             */
295
            
296
            local otherIdx = gTranscript.reports_.indexWhich
297
                ({x: x.ofKind(ImplicitActionAnnouncement)
298
                 && !x.action_.ofKind(TakeAction) && !x.isFailure });  
299
            
300
            if(otherIdx)       
301
            {
302
                /* 
303
                 *   if we're going to show some other implicit reports, it's
304
                 *   probably best to do so first
305
                 */
306
                firstImp = otherIdx + 1;    
307
                rep.messageText_ = 'taking ' + impTxt;
308
            }
309
            else
310
                rep.messageText_ = '<./p0>\n<.assume>first taking ' +
311
                impTxt + '<./assume>\n';
312
            
313
            /* 
314
             *   Prevent the implicitAnnouncementGrouper from overwriting the 
315
             *   text we've just stored.
316
             */
317
            rep.messageProp_ = nil;
318
            
319
            /*  
320
             *   Remove all the individual implicit take action 
321
             *   reports from the transcript.
322
             */
323
            gTranscript.reports_ = gTranscript.reports_.subset({x: !impFunc(x) });
324
            
325
            /*  
326
             *   Add back our summary implicit action report at the location 
327
             *   of the first individual report we removed.
328
             */
329
            gTranscript.reports_.insertAt(firstImp, rep);
330
            
331
            /*   
332
             *   Remove all the CommandReports relating to taking things,
333
             *   since they would otherwise show up now we've removed
334
             *   most of the implicit action reports.
335
             */
336
            gTranscript.reports_ = gTranscript.reports_.subset({
337
                x: !((x.ofKind(DefaultCommandReport) ||
338
                     x.ofKind(MainCommandReport))
339
                     && x.action_.ofKind(TakeAction)) } );
340
        }
341
    
342
       /* 
343
        *   After all the preliminary work we finally summarize the 
344
        *   successful default reports relating to the main action into a 
345
        *   single report. There's a complication with TAKE FROM since a 
346
        *   TAKE FROM action is eventually turned into a TAKE action, so we 
347
        *   need to handle this as a special case.
348
        */
349
        
350
        
351
        gTranscript.summarizeAction(
352
            { x: ((x.action_ == gAction) || (gActionIs(TakeFrom) &&
353
                                            x.action_.ofKind (TakeAction))) 
354
            && !x.isFailure },
355
            new function (vec)
356
        {       
357
            /* 
358
             *   Construct a string reporting the objects we did take, 
359
             *   ensuring that each one is counted only once.
360
             */
361
                       
362
            local dobjText = definiteObjectLister.makeSimpleList
363
                ( vec.applyAll({x: x.dobj_}).getUnique());
364
            
365
                        
366
            /* Then use this to construct a description of the action */                        
367
            return gAction.getActionDescWith(dobjText);                 
368
369
        });           
370
        
371
    }            
372
    
373
    /* 
374
     *   The processFailures method is provided as a hook for game authors 
375
     *   who want to process the failure messages (e.g, to summarize them 
376
     *   some way) further than this extension does (it just moves them all 
377
     *   to the end).
378
     *
379
     *   The vec parameter contains the vector of failure messages for 
380
     *   further processing. Note that this method won't be called at all 
381
     *   unless there are some failure messages in vec to process.
382
     *
383
     *   This method also won't be called unless summarizeFailures is true 
384
     *   (by default, it's nil).
385
     *
386
     *   This method should return a vector of CommandReports resulting from 
387
     *   whatever we wanted to do to the failure reports generated by the 
388
     *   library. By default we just return the vector that's passed to us.
389
     *
390
     *   The caller will automatically append the vector returned by this 
391
     *   method to the vector of successul reports.
392
     */
393
    
394
    processFailures(vec) { return vec; }
395
    
396
    /* 
397
     *   By default we don't run the processFailures routine at all. This 
398
     *   avoids pointless work when processFailures doesn't do anything, and 
399
     *   also allows authors to opt in to any future version of 
400
     *   processFailures that does do something.
401
     */
402
    summarizeFailures = nil
403
;
404
405
/* 
406
 *   This modification enables us to prevent the implicitAnnouncementGrouper 
407
 *   from overwriting a customized messageText_
408
 */
409
410
modify CommandAnnouncement
411
    getMessageText([params])
412
    {
413
        if(messageProp_)
414
            return gLibMessages.(messageProp_)(params...);
415
        else
416
            return messageText_;
417
    }    
418
;
419
420
421
/* 
422
 *   Service routines for describing an action given a string (dobjText) 
423
 *   containing a list of direct objects.
424
 *
425
 *   The complication is that the verb getVerbPhrase uses the verb in the
426
 *   present imperative form, e.g. 'TAKE'. This is wrong if the game is in the
427
 *   past tense or the actor is third person singular. We therefore need to
428
 *   correct for that.
429
 */
430
431
modify TAction
432
    getActionDescWith(dobjText)
433
    {
434
        return '{You/he} ' + vCheck(gAction.getVerbPhrase1(true, 
435
            gAction.verbPhrase, dobjText, nil)) + '.<.p>';    
436
    }
437
    
438
    verbName()
439
    {
440
        rexMatch(pat, verbPhrase);
441
        return rexGroup(1)[3];
442
    }
443
    
444
    vCheck(str)
445
    {
446
        local vName = verbName();
447
        local correctedVName = vCorrect(vName);
448
        if(vName != correctedVName)
449
            str = rexReplace('%<'+vName+'%>', str, correctedVName, ReplaceAll);
450
        
451
        return str;
452
    }
453
    
454
    /* 
455
     *   Put the verb into the correct tense and ensure that it agrees with 
456
     *   its subject. Subclasses may need to override depending on the verb,
457
     *   to add the appropriate verbEndingXXX property.
458
     */
459
    
460
    vCorrect(str)   {  return gActor.conjugateRegularVerb(str); }
461
    
462
    pat = static new RexPattern('(.*)(?=/)')
463
;
464
465
modify TIAction
466
    getActionDescWith(dobjText)
467
    {    
468
        return '{You/he} '+ vCheck(gAction.getVerbPhrase2(true, 
469
            gAction.verbPhrase, dobjText, nil, gIobj.theName)) + '.<.p>';
470
    }
471
;