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

4b825dc642cb6eb9a060e54bf8d69288fbee4904cfad47cfa334b206c65f22086bcc5d63e6f70944
1
#charset "us-ascii"
2
#include <adv3.h>
3
#include <en_us.h>
4
5
ModuleID
6
{
7
    name = 'TCommandTopic Library Extension'
8
    byline = 'by Eric Eve'
9
    htmlByline = 'by <a href="mailto:eric.eve@hmc.ox.ac.uk">Eric Eve</a>'
10
    version = '3.1'    
11
    listingOrder = 70
12
}
13
14
/* 
15
 *   VERSION HISTORY 
16
 *
17
 *   v3.1 01-Mar-08 - Adding handling on Actor to trap System Actions issued
18
 *   as commands to NPCs.
19
 *
20
 *   v3.0 08-Jul-07 - Added handling to ActorState to allow optional blocking
21
 *   of SystemActions and TopicActions directed at the Actor without needed
22
 *   to provide custom CommandTopics to trap them.
23
 *   Also enhanced CommandHelper.proper() to deal more intelligently with
24
 *   possessives.
25
 *
26
 *   v2.2 11-Dec-05 - Added handling to optionally give or restore initial 
27
 *   capitals to proper names used in cmdPhrase.
28
 *
29
 *   v2.1 10-Dec-05 - Added handling to CommandHelper to prevent run-time 
30
 *   error when constructing cmdPhrase in the case of a TopicAction type 
31
 *   command (e.g. fred, ask me about john).
32
 *
33
 *   v2.0 27-Mar-04 - Substantial changes:  
34
 *.- Move the matchTopic handling into CommandTopic 
35
 *.- Allow CommandTopic to match the indirect object as well as the direct 
36
 *   object of commands. 
37
 *.- Define a new CommandHelper 
38
 *   mix-in class to make it more convenient to get at the current command 
39
 *   action and its objects 
40
 *.- Define two new methods on CommandTopic, 
41
 *   handleAction(fromActor, action) and actionResponse(fromActor, action) 
42
 *   for convenience of command handling. 
43
 *.- Define new AltCommandTopic and 
44
 *   AltTCommandTopic classes  
45
 *
46
 *   v1.1 13-Mar-04 
47
 *.- Move handleTopic,  actionPhrase and currentAction into 
48
 *   CommandTopic 
49
 *.- Add an obeyCommand property to CommandTopic & 
50
 *   DefaultCommandTopic 
51
 *.- Add a currentIObj property to TCommandTopic 
52
 *.- Add the ExecCommandTopic class
53
 */ 
54
55
56
57
/* modify DefaultCommandTopic to make two new properties available */
58
59
modify DefaultCommandTopic
60
   handleTopic(fromActor, action)
61
   {
62
    getActor.lastCommandTopic_ = self; 
63
    
64
    handleAction(fromActor, action);
65
         
66
    inherited(fromActor, action);
67
    
68
    actionResponse(fromActor, action);
69
   }
70
   
71
   /* This method performs any handling we want before 
72
    * topicResponse; it's mainly here
73
    * to be overridden by CommandHelper, but can be used
74
    * for any purposes authors want.
75
    */
76
    
77
   handleAction(fromActor, action)
78
   {
79
      
80
   }
81
   
82
   /* This method can be overridden to provide responses
83
    * that need to know the issuing actor or the action
84
    * commanded. It is performed after topicResponse.
85
    */
86
    
87
   actionResponse(fromActor, action)
88
   {
89
     /* by default - do nothing */ 
90
   }
91
  
92
  matchScore = 3
93
  obeyCommand = nil
94
;
95
96
modify CommandTopic
97
  /* 
98
   * The direct object, or a list of direct objects, that will be matched
99
   * by this topic. If this is left at nil, any direct object or none
100
   * will be matched.
101
   */
102
  matchDobj = nil
103
  
104
  /* 
105
   * The indirect object, or a list of indirect objects, that will be matched
106
   * by this topic. If this is left at nil, any indirect object or none
107
   * will be matched.
108
   */
109
  matchIobj = nil
110
  
111
  matchTopic(fromActor, action)
112
  {
113
    /* First check whether we match the action of the command */
114
    if(!inherited(fromActor, action))
115
      return nil;
116
        
117
    /* Then check whether we match the direct object of the command action*/
118
        
119
    if(matchDobj != nil && !matchObjs(action.getDobj, matchDobj))    
120
         return nil;
121
    
122
    /* Finally, check whether we match its indirect object */
123
    
124
    if(matchIobj != nil && !matchObjs(action.getIobj, matchIobj))
125
         return nil;
126
    
127
    
128
     /* We've matched everything we should, so return matchScore */
129
   return matchScore; 
130
  }
131
 
132
  matchObjs(obj, objToMatch)
133
  {
134
    if(objToMatch.ofKind(Collection))
135
    {
136
      return objToMatch.indexWhich({x: obj.ofKind(x)});          
137
    }
138
    else    
139
    {
140
      return obj.ofKind(objToMatch);      
141
    }
142
  }
143
144
  
145
  handleTopic(fromActor, action)
146
  {
147
    /* Tell our actor that this is the commandTopic that has
148
     * been matched and is responding.
149
     */  
150
    getActor.lastCommandTopic_ = self; 
151
    
152
    handleAction(fromActor, action);
153
         
154
    inherited(fromActor, action);
155
    
156
    actionResponse(fromActor, action);
157
  }  
158
  
159
   /* This method performs any handling we want before 
160
    * topicResponse; it's mainly here
161
    * to be overridden by CommandHelper, but can be used
162
    * for any purposes authors want.
163
    */  
164
  handleAction(fromActor, action)
165
   {
166
      
167
   }
168
  
169
  
170
  /*
171
   *  An additional method for providing customised responses
172
   *  that provides ready access to the fromActor and action parameters
173
   */ 
174
    
175
  actionResponse(fromActor, action)
176
  {
177
     /* by default - do nothing */ 
178
  }
179
  
180
   
181
  /* Set this to true if you want the actor to obey the command just as
182
   * it was issued */
183
  obeyCommand = nil 
184
  
185
;
186
187
/* Mix-in Class for use with CommandTopic and DefaultCommandTopic 
188
 * Principally designed to cache the currently commanded action
189
 * and its most commonly-useful properties. This is mainly for
190
 * convenience, and allows the additional properties to be accessed
191
 * from topicResponse.
192
 */
193
CommandHelper : object
194
   handleAction(fromActor, action)
195
   {
196
     cmdDobj = action.getDobj;
197
     cmdIobj = action.getIobj;   
198
     cmdTopic = action.getTopic;
199
     cmdAction = action;
200
     cmdPhrase = proper(action.ofKind(TopicActionBase) ? topicCmdPhrase : youToMe(action.getInfPhrase));
201
   }
202
   cmdDobj = nil
203
   cmdIobj = nil
204
   cmdAction = nil
205
   cmdPhrase = nil  
206
   cmdTopic = nil
207
   topicCmdPhrase
208
   {
209
      /* 
210
       *   This is a hack to get round the fact that 
211
       *   TopicTAction.getVerbPhrase() references gTopicText, which in turn 
212
       *   references gAction (which is the wrong action at this point), so 
213
       *   we temporarily need gAction to refer to the action commanded, not 
214
       *   the EventAction which is the real gAction at this point.
215
       */
216
    
217
      local oldAction;
218
      try
219
      {
220
         oldAction = gAction;
221
         gAction = cmdAction;         
222
         return youToMe(cmdAction.getInfPhrase);
223
      }
224
      finally
225
      {
226
         gAction = oldAction;
227
      }      
228
   }
229
   
230
   /* Takes a string and replaces any occurrence of a word that exists in
231
    * the list of properNames with the same word with an initial capital.
232
    */ 
233
   
234
   proper(str)
235
   {
236
     foreach(local name in nilToList(properNames))
237
    {
238
      local name1 = ' ' + name;
239
      local name2 = ' ' + name.substr(1,1).toUpper + name.substr(2);      
240
      str = str.findReplace(name1, name2, ReplaceAll);      
241
    }
242
    
243
        /* 
244
         *   If the current npc is mentioned in the possessive, change this 
245
         *   to 'your'; also replace the npc's name with 'yourself'.
246
         */
247
        
248
        str = langMessageBuilder.generateMessage(str);
249
        str = rexReplace('%<' + getActor.theName + '<`|squote>s%>', str, 'your', 
250
                              ReplaceAll);  
251
        str = rexReplace('%<' + getActor.theName + '%>', str, 'yourself', 
252
                         ReplaceAll);
253
        
254
    return str;
255
   }
256
   
257
   /* 
258
    *   Modify this to contain a list of proper names that you want to 
259
    *   appear with an initial capital if they appear in a cmdPhrase
260
    */
261
   properNames = []
262
;
263
264
function youToMe(str)
265
{
266
  if(str.ofKind(String))
267
  {       
268
   str = str.findReplace(' you ', ' me ', ReplaceAll);
269
    if(str.endsWith(' you'))
270
      str = str.findReplace(' you', ' me', ReplaceOnce,
271
        str.length-5);
272
  }
273
  return str.toLower();      
274
}
275
276
277
/*
278
 * TCommandTopic is a CommandTopic that can match a particular direct
279
 * object or set of direct objects as well as a particular action or
280
 * set of actions. This allows the TCommandTopic's topicResponse()
281
 * method either to display a message specific to a particular action
282
 * and direct object, but also, optionally, to define the actions
283
 * the target actor takes in response.
284
 *
285
 * For example, to define a TCommandTopic that matches the command
286
 * >BOB, TAKE THE GOLD COIN 
287
 *       or
288
 * >BOB, TAKE THE DIAMOND
289
 *
290
 * and then displays a suitable conversational interchange and
291
 * had Bob take the gold coin you might define:
292
 *
293
 *    + TCommandTopic @TakeAction
294
 *       matchDobj = [goldCoin, diamond]
295
 *       topicResponse()
296
 *       {
297
 *         "<q>Bob, <<cmdPhrase>>, would you?</q>, you ask.\b
298
 *          <q>Sure,</q> he agrees readily. ";
299
 *          nestedActorAction(getActor, Take, currentDobj);
300
 *       }
301
 *    ;
302
 */
303
304
class TCommandTopic : CommandHelper, CommandTopic
305
;
306
307
class DefaultTCommandTopic : CommandHelper, DefaultCommandTopic
308
;
309
310
311
modify Actor  
312
    /* Keep track of the last CommandTopic triggered on this actor */
313
    lastCommandTopic_ = nil
314
    
315
    /* By default, trap system commands targeted to NPCs */
316
    obeyCommand(issuingActor, action)
317
    {
318
        if(action.ofKind(SystemAction) && obeySystemCommands == nil)
319
        {   
320
            systemActionToNPC();
321
            return nil;
322
        }
323
        
324
        return inherited(issuingActor, action);
325
    }
326
    
327
    systemActionToNPC() { libMessages.systemActionToNPC(); }
328
    obeySystemCommands = nil
329
;
330
331
/* 
332
  *  Modification to ActorState.obeyCommand() to make it work with 
333
  *  TCommandTopic. This modification affects only actions that
334
  *  have one or more direct objects, and does the following:
335
  *
336
  *  1) If the action has more than one direct object, it is 
337
  *     split into a series of actions each having only one
338
  *     direct object, taking each direct object in turn.
339
  *     This allows the game author to assume that any
340
  *     TCommandTopic will only have one direct object to
341
  *     deal with at a time, facilitating the handling when
342
  *     the author may wish to have the target actor make
343
  *     different responses where the direct objects are different.
344
  *
345
  *  2) For each action with a direct object passed to handleConversation
346
  *     (and then to a CommandTopic, TCommandTopic or DefaultCommandTopic)
347
  *     the current direct object is marked as such on the action object
348
  *     passed; this allows CommandTopic, TCommandTopic and DefaultCommandTopic
349
  *     to use methods such as getDobj and getInfPhrase that assume that
350
  *     the direct object is defined. 
351
  */
352
  
353
modify ActorState
354
  obeyCommand(issuingActor, action)
355
     {
356
         /* 
357
          * Reset the lastCommandTopic to nil in case the later
358
          * handling bypasses setting it, and we're left with
359
          * the spurious residue of a previous command.
360
          */
361
         
362
         getActor.lastCommandTopic_ = nil;
363
364
        
365
        /* 
366
         *   If we direct a SystemAction or a TopicAction at the actor, it 
367
         *   should normally be blocked. Commands like BOB, SAVE or BOB, ASK 
368
         *   SALLY ABOUT FRED are unlikely to have satisfactory handling, 
369
         *   and will typically produce suboptimal responses in 
370
         *   DefaultCommandTopics, so it's easiest to block them globally 
371
         *   here. We also provide a couple of flags to allow this to be 
372
         *   easily overridden on a per-state basis if desired.
373
         */
374
        
375
        
376
        if(autoBlockSystemCommands && action.ofKind(SystemAction))
377
        {
378
            explainBlockSystemCommand();
379
            return nil;
380
        }
381
        
382
        if(autoBlockTopicCommands && action.ofKind(TopicActionBase))
383
        {
384
            explainBlockTopicCommand();
385
            return nil;
386
        }
387
         /* 
388
          *  If the action takes a direct object (and so could have a list
389
          *  of direct objects), split it into a series of actions
390
          *  with a single direct object
391
          */
392
          
393
          
394
          local dobjLst = action.getResolvedDobjList;
395
396
          if(dobjLst != nil) 
397
         {
398
            local singleAction = action;
399
 
400
            local iobjLst = action.getResolvedIobjList;
401
            if(iobjLst != nil)
402
               singleAction.iobjCur_ = iobjLst[1];
403
                
404
 			foreach(local obj in dobjLst)
405
           {
406
              singleAction.dobjCur_ = obj;
407
              handleConversation(issuingActor, singleAction, commandConvType); 
408
           }
409
         }
410
411
         /* Otherwise, just treat it as a single command */
412
413
         else
414
           handleConversation(issuingActor, action, commandConvType);
415
 
416
         /* 
417
          * check whether to accept or refuse the command, based on
418
          *  the decision of the CommandTopic matched
419
          */
420
         return getActor.lastCommandTopic_ != nil
421
             && getActor.lastCommandTopic_.obeyCommand;
422
     }
423
    
424
    autoBlockSystemCommands  = true
425
    explainBlockSystemCommand() { playerMessages.systemActionToNPC(); }
426
    
427
    autoBlockTopicCommands = true
428
    explainBlockTopicCommand()
429
    {
430
        "It d{oes|id}n't make much sense to ask <<getActor.itObj>> to do that. ";
431
    }
432
;
433
434
435
/* AltCommandTopic is an AltTopic with the a special handleTopic
436
 * method to facilitate the handling of commands.
437
 */
438
439
AltCommandTopic : AltTopic
440
handleTopic(fromActor, action)
441
  {
442
    
443
    getActor.lastCommandTopic_ = self; 
444
    
445
    handleAction(fromActor, action);
446
         
447
    inherited(fromActor, action);
448
    
449
    actionResponse(fromActor, action);
450
  }  
451
  
452
  handleAction(fromActor, action)
453
   {
454
      
455
   }
456
  
457
  
458
  /*
459
   *  An additional method for providing customised responses
460
   *  that provides ready access to the fromActor and action parameters
461
   */ 
462
    
463
  actionResponse(fromActor, action)
464
  {
465
     /* by default - do nothing */ 
466
  }
467
  
468
   
469
  /* Set this to true if you want the actor to obey the command just as
470
   * it was issued */
471
  obeyCommand = nil 
472
  
473
;
474
475
AltTCommandTopic : CommandHelper, AltCommandTopic
476
;
477
478
479
480
class SuggestedCommandTopic : SuggestedTopic
481
   suggestionGroup = [suggestionCommandGroup]
482
   fullName = ('tell {it targetActor/him} to ' + name)
483
;
484
485
suggestionCommandGroup : SuggestionListGroup
486
     groupPrefix = "tell {it targetActor/him} to "
487
;
488
489
490
491
492