| | 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 | ; |