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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
3
#include <adv3.h>
4
#include <en_us.h>
5
6
  /* 
7
   *   smartAccompany.t
8
   *
9
   *   by Eric Eve
10
   *
11
   *   version 1.2; 04-May-09
12
   *
13
   *   Version 1.2 fixes a bug that can cause a run-time error when a report 
14
   *   in the transcript doesn't have a messageText_ property. 
15
   *
16
   *   Version 1.1 fixes a bug that could cause a run-time error when travel 
17
   *   between rooms is disallowed.
18
   *
19
   *   A small extension that improves the ordering and grouping of reports 
20
   *   from an NPC in an AccompanyingState following the player character. 
21
   *   For a full explanation of what this extension does, and how it works, 
22
   *   see Example 3 in the Tech Man article on Manipulating the Transcript.
23
   */
24
25
ModuleID
26
    name = 'smartAccompany'
27
    byline = 'by Eric Eve'
28
    htmlByline = 'by <a href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>'
29
    version = '1.2'
30
    listingOrder = 75
31
;
32
33
/*  
34
 *   Combine reports of the NPC's actions in the old location into a single 
35
 *   sentence and move it to just before the new room description.
36
 */
37
38
modify AccompanyingState
39
    beforeTravel(traveler, connector)
40
    {
41
        gAction.callAfterActionMain(self); 
42
        inherited(traveler, connector);
43
    }
44
    
45
    afterActionMain()
46
    {
47
        /* 
48
         *   Find the insertion point, which is just before the description 
49
         *   of the new room.
50
         */
51
        
52
        local idx = gTranscript.reports_.indexWhich({ x: x.messageText_ ==
53
                                              '<.roomname>' });
54
        
55
        /* 
56
         *   If there is no insertion point, travel failed for some reason, 
57
         *   in which case we don't want to change the transcript at all.
58
         */        
59
        
60
        if(idx == nil)
61
            return;
62
        
63
        local actor = getActor;       
64
        local str = '';
65
        local vec = new Vector(4);   
66
        local pat = new RexPattern('<NoCase>^' + actor.theName + '<Space>+');
67
        
68
        /*  
69
         *   Look through all the reports in the transcript for those whose 
70
         *   message text begins with the name of our actor, followed by at 
71
         *   least one space (so that if the actor's name is Rob we don't 
72
         *   pick up any messages relating to Roberta, for example), 
73
         *   regardless of case (so we pick up messages about 'the tall man' 
74
         *   and 'The tall man'). 
75
         *
76
         *   Once we get to the description of a new room, stop looking (we 
77
         *   don't want to include the description of the actor arriving in 
78
         *   the new location, just the actor departing the old one).
79
         *
80
         *   Store the relevant message strings (those before the new room 
81
         *   description, but not any after it) in the vector vec; at the 
82
         *   same time remove these reports from the transcript (since we'll 
83
         *   be replacing them with a single report below).
84
         */
85
        
86
        while((idx = gTranscript.reports_.indexWhich({ x: x.messageText_ != nil
87
            && rexMatch(pat, x.messageText_) })) != nil)
88
        {
89
            if(idx > gTranscript.reports_.indexWhich({ x: x.messageText_ ==
90
                                              '<.roomname>' }))
91
               break;  
92
93
            
94
            vec.append(gTranscript.reports_[idx].messageText_);
95
            gTranscript.reports_.removeElementAt(idx);
96
        }
97
        
98
        local len = actor.theName.length() + 1;
99
        
100
        /* 
101
         *   Now go through each of the strings in vec in turn, stripping 
102
         *   off the actor's name at the start and the period/full-stop at 
103
         *   the end (so that, for example 'Bob stands up. ' becomes 'stands 
104
         *   up'. In searching for the position of the terminating full 
105
         *   stop (period) we start beyond the end of the actor's name in 
106
         *   case the actor's name contains a full stop (e.g. Prof. Smith).
107
         */
108
        
109
        vec.applyAll( { cur: cur.substr(len, cur.find('.', len) - len)});
110
        
111
        /*   
112
         *   Concatanate these truncated action reports into a single string 
113
         *   listing the actor's actions (e.g. 'stands up and comes with 
114
         *   you'), separating the final pair of actions with 'and' and any 
115
         *   previous actions with a comma. We can use stringLister (defined 
116
         *   in the previous example) to do this for us.
117
         */        
118
        
119
        str = stringLister.makeSimpleList(vec.toList);
120
        
121
        /* 
122
         *   Put the actor's name back at the start of the string, and 
123
         *   conclude the string with a full-stop and a paragraph break.
124
         */
125
        str = '\^' + actor.theName + ' ' + str + '.<.p>';
126
        
127
        /* 
128
         *   Find the insertion point, which is just before the description 
129
         *   of the new room. This may have changed as a result of our 
130
         *   manipulations above.
131
         */
132
        
133
        idx = gTranscript.reports_.indexWhich({ x: x.messageText_ ==
134
                                              '<.roomname>' });
135
                
136
        /* 
137
         *   Insert the message we just created as a new report at the 
138
         *   appropriate place, i.e. just before the new room description.
139
         */
140
        gTranscript.reports_.insertAt(idx, new MessageResult(str));
141
        
142
    }
143
;
144
145
/* 
146
 *   The modifications on AccompanyingState probably aren't so suitable for a 
147
 *   GuidedTourState, so we make afterActionMain() do nothing by default. This
148
 *   can be overridden by game authors if desired.
149
 */
150
151
modify GuidedTourState    
152
    afterActionMain() { }
153
;
154
155
/*
156
 *   Ensure that the default sayDeparting() message from 
157
 *   AccompanyingInTravelState is added to the transcript in a single report, 
158
 *   starting with the NPC's name. 
159
 */
160
161
modify AccompanyingInTravelState
162
    sayDeparting(conn)
163
    {
164
        local msg = mainOutputStream.captureOutput( {: inherited(conn) });;
165
        if(msg.startsWith('\^'))
166
            msg = msg.substr(2);
167
        mainReport(msg);
168
    }
169
;
170