| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library: command reports |
| | 7 | * |
| | 8 | * This module defines the "command report" classes, which the command |
| | 9 | * execution engine uses to keep track of the status of a command. |
| | 10 | */ |
| | 11 | |
| | 12 | #include "adv3.h" |
| | 13 | |
| | 14 | |
| | 15 | /* ------------------------------------------------------------------------ */ |
| | 16 | /* |
| | 17 | * Command report objects. The library uses these to control how the |
| | 18 | * text from a command is displayed. Game code can also use report |
| | 19 | * objects to show and control command results, but this isn't usually |
| | 20 | * necessary; game code can usually simply display messages directly. |
| | 21 | * |
| | 22 | * Reports are divided into two broad classes: "default" and "full" |
| | 23 | * reports. |
| | 24 | * |
| | 25 | * A "default" report is one that simply confirms that an action was |
| | 26 | * performed, and provides little additional information. The library |
| | 27 | * uses default reports for simple commands whose full implications |
| | 28 | * should normally be obvious to a player typing such commands: take, |
| | 29 | * drop, put in, and the like. The library's default reports are |
| | 30 | * usually quite terse: "Taken", "Dropped", "Done". |
| | 31 | * |
| | 32 | * A "full" report is one that gives the player more information than a |
| | 33 | * simple confirmation. These reports typically describe either the |
| | 34 | * changes to the game state caused by a command or surprising side |
| | 35 | * effects of a command. For example, if the command is "push button," |
| | 36 | * and pushing the button opens the door next to the button, a full |
| | 37 | * report would describe the door opening. |
| | 38 | * |
| | 39 | * Note that a full report is warranted any time a command describes |
| | 40 | * anything beyond a simple confirmation. In our door-opening button |
| | 41 | * example, opening the door by pushing the button always warrants a |
| | 42 | * full report, even if the player has already seen the effects of the |
| | 43 | * button a hundred times before, and even if the button is labeled |
| | 44 | * "push to open door." It doesn't matter whether or not the |
| | 45 | * consequences of the command ought to be obvious to the player; what |
| | 46 | * matters is that the command warrants a report beyond a simple |
| | 47 | * confirmation. Any time a report is more than a simple confirmation, |
| | 48 | * it is a full report, no matter how obvious to the player the effects |
| | 49 | * of the action. |
| | 50 | * |
| | 51 | * Full reports are further divided into three subcategories by time |
| | 52 | * ordering: "main," "before," and "after." "Before" and "after" |
| | 53 | * reports are ordered before and after (respectively) a main report. |
| | 54 | */ |
| | 55 | class CommandReport: object |
| | 56 | construct() |
| | 57 | { |
| | 58 | /* |
| | 59 | * remember the action with which we're associated, unless a |
| | 60 | * subclass already specifically set the action |
| | 61 | */ |
| | 62 | if (action_ == nil) |
| | 63 | action_ = gAction; |
| | 64 | } |
| | 65 | |
| | 66 | /* get/set my action */ |
| | 67 | getAction() { return action_; } |
| | 68 | setAction(action) { action_ = action; } |
| | 69 | |
| | 70 | /* check to see if my action is implicit */ |
| | 71 | isActionImplicit() { return action_ != nil && action_.isImplicit; } |
| | 72 | |
| | 73 | /* check to see if my action is nested in the other report's action */ |
| | 74 | isActionNestedIn(other) |
| | 75 | { |
| | 76 | return (action_ != nil |
| | 77 | && other.getAction() != nil |
| | 78 | && action_.isNestedIn(other.getAction())); |
| | 79 | } |
| | 80 | |
| | 81 | /* |
| | 82 | * Flag: if this property is true, this report indicates a failure. |
| | 83 | * By default, a report does not indicate failure. |
| | 84 | */ |
| | 85 | isFailure = nil |
| | 86 | |
| | 87 | /* |
| | 88 | * Flag: if this property is true, this report indicates an |
| | 89 | * interruption for interactive input. |
| | 90 | */ |
| | 91 | isQuestion = nil |
| | 92 | |
| | 93 | /* iteration number current when we were added to the transcript */ |
| | 94 | iter_ = nil |
| | 95 | |
| | 96 | /* the action I'm associated with */ |
| | 97 | action_ = nil |
| | 98 | |
| | 99 | /* |
| | 100 | * Am I part of the same action as the given report? Returns true if |
| | 101 | * this action is part of the same iteration and part of the same |
| | 102 | * action as the other report. |
| | 103 | */ |
| | 104 | isPartOf(report) |
| | 105 | { |
| | 106 | /* |
| | 107 | * if I don't have an action, or the other report doesn't have an |
| | 108 | * action, we're not related |
| | 109 | */ |
| | 110 | if (action_ == nil || report.action_ == nil) |
| | 111 | return nil; |
| | 112 | |
| | 113 | /* if our iterations don't match, we're not related */ |
| | 114 | if (iter_ != report.iter_) |
| | 115 | return nil; |
| | 116 | |
| | 117 | /* check if I'm part of the other report's action */ |
| | 118 | return action_.isPartOf(report.action_); |
| | 119 | } |
| | 120 | ; |
| | 121 | |
| | 122 | /* |
| | 123 | * Group separator. This simply displays separation between groups of |
| | 124 | * messages - that is, between one set of messages associated with a |
| | 125 | * single action and a set of messages associated with a different |
| | 126 | * action. |
| | 127 | */ |
| | 128 | class GroupSeparatorMessage: CommandReport |
| | 129 | construct(report) |
| | 130 | { |
| | 131 | /* use the same action and iteration as the given report */ |
| | 132 | action_ = report.getAction(); |
| | 133 | iter_ = report.iter_; |
| | 134 | } |
| | 135 | |
| | 136 | /* show the normal command results separator */ |
| | 137 | showMessage() { say(gLibMessages.complexResultsSeparator); } |
| | 138 | ; |
| | 139 | |
| | 140 | /* |
| | 141 | * Internal separator. This displays separation within a group of |
| | 142 | * messages for a command, to visually separate the results from an |
| | 143 | * implied command from the results for the enclosing command. |
| | 144 | */ |
| | 145 | class InternalSeparatorMessage: CommandReport |
| | 146 | construct(report) |
| | 147 | { |
| | 148 | /* use the same action and iteration as the given report */ |
| | 149 | action_ = report.getAction(); |
| | 150 | iter_ = report.iter_; |
| | 151 | } |
| | 152 | |
| | 153 | /* show the normal command results separator */ |
| | 154 | showMessage() { say(gLibMessages.internalResultsSeparator); } |
| | 155 | ; |
| | 156 | |
| | 157 | /* |
| | 158 | * Report boundary marker. This is a pseudo-report that doesn't display |
| | 159 | * anything; its purpose is to allow a caller to identify a block of |
| | 160 | * reports (the reports between two markers) for later removal or |
| | 161 | * reordering. |
| | 162 | */ |
| | 163 | class MarkerReport: CommandReport |
| | 164 | showMessage() { } |
| | 165 | ; |
| | 166 | |
| | 167 | /* |
| | 168 | * End-of-description marker. This serves as a marker in the transcript |
| | 169 | * stream to let us know where the descriptive reports for a given |
| | 170 | * action end. |
| | 171 | */ |
| | 172 | class EndOfDescReport: MarkerReport |
| | 173 | ; |
| | 174 | |
| | 175 | /* |
| | 176 | * Simple MessageResult-based command report |
| | 177 | */ |
| | 178 | class CommandReportMessage: CommandReport, MessageResult |
| | 179 | construct([params]) |
| | 180 | { |
| | 181 | /* invoke our base class constructors */ |
| | 182 | inherited CommandReport(); |
| | 183 | inherited MessageResult(params...); |
| | 184 | } |
| | 185 | ; |
| | 186 | |
| | 187 | /* |
| | 188 | * default report |
| | 189 | */ |
| | 190 | class DefaultCommandReport: CommandReportMessage |
| | 191 | ; |
| | 192 | |
| | 193 | /* |
| | 194 | * extra information report |
| | 195 | */ |
| | 196 | class ExtraCommandReport: CommandReportMessage |
| | 197 | ; |
| | 198 | |
| | 199 | /* |
| | 200 | * default descriptive report |
| | 201 | */ |
| | 202 | class DefaultDescCommandReport: CommandReportMessage |
| | 203 | ; |
| | 204 | |
| | 205 | /* |
| | 206 | * cosmetic spacing report |
| | 207 | */ |
| | 208 | class CosmeticSpacingCommandReport: CommandReportMessage |
| | 209 | ; |
| | 210 | |
| | 211 | /* |
| | 212 | * base class for all "full" reports |
| | 213 | */ |
| | 214 | class FullCommandReport: CommandReportMessage |
| | 215 | /* |
| | 216 | * a full report has a sequence number that tells us where the |
| | 217 | * report goes relative to the others - the higher this number, the |
| | 218 | * later the report goes |
| | 219 | */ |
| | 220 | seqNum = nil |
| | 221 | ; |
| | 222 | |
| | 223 | /* |
| | 224 | * "before" report - these come before the main report |
| | 225 | */ |
| | 226 | class BeforeCommandReport: FullCommandReport |
| | 227 | seqNum = 1 |
| | 228 | ; |
| | 229 | |
| | 230 | /* |
| | 231 | * main report |
| | 232 | */ |
| | 233 | class MainCommandReport: FullCommandReport |
| | 234 | seqNum = 2 |
| | 235 | ; |
| | 236 | |
| | 237 | /* |
| | 238 | * failure report |
| | 239 | */ |
| | 240 | class FailCommandReport: FullCommandReport |
| | 241 | seqNum = 2 |
| | 242 | isFailure = true |
| | 243 | ; |
| | 244 | |
| | 245 | /* |
| | 246 | * failure marker - this is a silent report that marks an action as |
| | 247 | * having failed without actually generating any message text |
| | 248 | */ |
| | 249 | class FailCommandMarker: MarkerReport |
| | 250 | isFailure = true |
| | 251 | ; |
| | 252 | |
| | 253 | /* |
| | 254 | * "after" report - these come after the main report |
| | 255 | */ |
| | 256 | class AfterCommandReport: FullCommandReport |
| | 257 | seqNum = 3 |
| | 258 | ; |
| | 259 | |
| | 260 | /* |
| | 261 | * An interruption for interactive input. This is used to report a |
| | 262 | * prompt for more information that's needed before the command can |
| | 263 | * proceed, such as a prompt for a missing object, or a disambiguation |
| | 264 | * prompt. |
| | 265 | */ |
| | 266 | class QuestionCommandReport: MainCommandReport |
| | 267 | isQuestion = true; |
| | 268 | ; |
| | 269 | |
| | 270 | /* |
| | 271 | * A conversation begin/end report. This is a special marker we insert |
| | 272 | * into the transcript to flag the boundaries of an NPC's conversational |
| | 273 | * message. |
| | 274 | */ |
| | 275 | class ConvBoundaryReport: CommandReport |
| | 276 | construct(id) { actorID = id; } |
| | 277 | |
| | 278 | /* the actor's ID number, as assigned by the ConversationManager */ |
| | 279 | actorID = nil |
| | 280 | ; |
| | 281 | class ConvBeginReport: ConvBoundaryReport |
| | 282 | showMessage() { say('<.convbegin ' + actorID + '>'); } |
| | 283 | ; |
| | 284 | class ConvEndReport: ConvBoundaryReport |
| | 285 | construct(id, node) |
| | 286 | { |
| | 287 | inherited(id); |
| | 288 | defConvNode = node; |
| | 289 | } |
| | 290 | |
| | 291 | showMessage() |
| | 292 | { |
| | 293 | if (actorID != nil) |
| | 294 | say('<.convend ' + actorID + ' ' |
| | 295 | + (defConvNode == nil ? '' : defConvNode) + '>'); |
| | 296 | } |
| | 297 | |
| | 298 | /* the default new ConvNode for the actor */ |
| | 299 | defConvNode = nil |
| | 300 | ; |
| | 301 | |
| | 302 | /* ------------------------------------------------------------------------ */ |
| | 303 | /* |
| | 304 | * Announcements. We use these to track announcements to be made as |
| | 305 | * part of an action's results. |
| | 306 | */ |
| | 307 | class CommandAnnouncement: CommandReport |
| | 308 | construct([params]) |
| | 309 | { |
| | 310 | /* inherit default handling */ |
| | 311 | inherited(); |
| | 312 | |
| | 313 | /* remember our text */ |
| | 314 | messageText_ = getMessageText(params...); |
| | 315 | } |
| | 316 | |
| | 317 | /* |
| | 318 | * Get our message text. By default, we simply get the gLibMessages |
| | 319 | * message given by the property. |
| | 320 | */ |
| | 321 | getMessageText([params]) |
| | 322 | { |
| | 323 | /* get the library message */ |
| | 324 | return gLibMessages.(messageProp_)(params...); |
| | 325 | } |
| | 326 | |
| | 327 | /* |
| | 328 | * Show our message. Our default implementation shows the library |
| | 329 | * message given by our messageProp_ property, using the parameters |
| | 330 | * we stored in our constructor. |
| | 331 | */ |
| | 332 | showMessage() |
| | 333 | { |
| | 334 | /* call gLibMessages to show our message */ |
| | 335 | say(messageText_); |
| | 336 | } |
| | 337 | |
| | 338 | /* our gLibMessages property */ |
| | 339 | messageProp_ = nil |
| | 340 | |
| | 341 | /* our message text */ |
| | 342 | messageText_ = '' |
| | 343 | ; |
| | 344 | |
| | 345 | /* |
| | 346 | * Multiple-object announcement. When the player applies a single |
| | 347 | * command to a series of objects (as in "take the book and the folder" |
| | 348 | * or "take all"), we'll show one of these announcements for each object, |
| | 349 | * just before we execute the command for that object. This announcement |
| | 350 | * usually just shows the object's name plus suitable punctuation (in |
| | 351 | * English, a colon), and helps the player see which results go with |
| | 352 | * which objects. |
| | 353 | */ |
| | 354 | class MultiObjectAnnouncement: CommandAnnouncement |
| | 355 | /* show the announceMultiActionObject message */ |
| | 356 | messageProp_ = &announceMultiActionObject |
| | 357 | ; |
| | 358 | |
| | 359 | /* |
| | 360 | * Default object announcement. We display this announcement whenever |
| | 361 | * the player leaves out a required object from a command, but the parser |
| | 362 | * is able to infer which object they must have meant. The parser infers |
| | 363 | * that an object was intended when a verb requires an object that the |
| | 364 | * player didn't specify, and there's only one logical choice for the |
| | 365 | * missing object. We announce our assumption to put it out in the open, |
| | 366 | * to ensure that the player is immediately alerted if they had something |
| | 367 | * else in mind. |
| | 368 | * |
| | 369 | * In English, this type of announcement conventionally consists of |
| | 370 | * simply the name of the assumed object, in parenthesis and on a line by |
| | 371 | * itself. In cases where the object role involves a prepositional |
| | 372 | * phrase in the verb structure, we generally show the preposition before |
| | 373 | * the object name. This format usually reads intuitively, by combining |
| | 374 | * with the text just above of the player's own command: |
| | 375 | * |
| | 376 | *. >open |
| | 377 | *. (the door> |
| | 378 | *. You try opening the door, but it seems to be locked. |
| | 379 | *. |
| | 380 | *. >unlock the door |
| | 381 | * (with the key) |
| | 382 | */ |
| | 383 | class DefaultObjectAnnouncement: CommandAnnouncement |
| | 384 | construct(obj, whichObj, action, allResolved) |
| | 385 | { |
| | 386 | /* remember our object */ |
| | 387 | obj_ = obj; |
| | 388 | |
| | 389 | /* remember the message parameters */ |
| | 390 | whichObj_ = whichObj; |
| | 391 | allResolved_ = allResolved; |
| | 392 | |
| | 393 | /* remember my action */ |
| | 394 | action_ = action; |
| | 395 | |
| | 396 | /* inherit default handling */ |
| | 397 | inherited(); |
| | 398 | } |
| | 399 | |
| | 400 | /* get our message text */ |
| | 401 | getMessageText() |
| | 402 | { |
| | 403 | /* get the announcement message from our object */ |
| | 404 | return obj_.announceDefaultObject(whichObj_, action_, allResolved_); |
| | 405 | } |
| | 406 | |
| | 407 | /* our defaulted object */ |
| | 408 | obj_ = nil |
| | 409 | |
| | 410 | /* our message parameters */ |
| | 411 | whichObj_ = nil |
| | 412 | allResolved_ = nil |
| | 413 | ; |
| | 414 | |
| | 415 | /* |
| | 416 | * Ambiguous object announcement. We display this when the parser |
| | 417 | * manages to resolve a noun phrase to an object (or objects) from an |
| | 418 | * ambiguous set of possibilities, without having to ask the player for |
| | 419 | * help but also without absolute certainty that the objects selected are |
| | 420 | * the ones the player meant. This happens when more than enough objects |
| | 421 | * are logical possibilities for selection, but some objects are more |
| | 422 | * logical choices than others. The parser picks the most logical of the |
| | 423 | * available options, but since other logical choices are present, the |
| | 424 | * parser can't be certain that it chose the ones the player actually |
| | 425 | * meant. Because of this uncertainty, we generate one of these |
| | 426 | * announcements each time this happens. This report lets the player |
| | 427 | * know exactly which object we chose, which will immediately alert the |
| | 428 | * player when our selection is different from what they had in mind. |
| | 429 | * |
| | 430 | * In form, this type of announcement usually looks just like a default |
| | 431 | * object announcement. |
| | 432 | */ |
| | 433 | class AmbigObjectAnnouncement: CommandAnnouncement |
| | 434 | /* show the announceAmbigObject announcement */ |
| | 435 | messageProp_ = &announceAmbigActionObject |
| | 436 | ; |
| | 437 | |
| | 438 | /* |
| | 439 | * Remapped action announcement. This is used when we need to mention a |
| | 440 | * defaulted or disambiguated object, but the player's original input was |
| | 441 | * remapped to a different action that rearranges the object roles. In |
| | 442 | * these cases, rather than just announcing the defaulted object name, we |
| | 443 | * announce the entire remapped action; we show the full action |
| | 444 | * description because rearrangement of the object roles usually makes |
| | 445 | * the standard object-only announcement confusing to read, since it |
| | 446 | * doesn't naturally fit in as a continuation of what the user typed. |
| | 447 | * |
| | 448 | * In English, this message is usually shown with the entire verb phrase, |
| | 449 | * in present participle form ("opening the door"), enclosed in |
| | 450 | * parentheses and on a line by itself. |
| | 451 | */ |
| | 452 | class RemappedActionAnnouncement: CommandAnnouncement |
| | 453 | construct() |
| | 454 | { |
| | 455 | /* use the action as the message parameter */ |
| | 456 | inherited(gAction); |
| | 457 | } |
| | 458 | |
| | 459 | messageProp_ = &announceRemappedAction |
| | 460 | ; |
| | 461 | |
| | 462 | /* |
| | 463 | * Each language module must define a class called |
| | 464 | * ImplicitAnnouncementContext, and three instances of the class, for use |
| | 465 | * by the generic library. The language module can define other |
| | 466 | * instances of the context class as needed. We minimally need the |
| | 467 | * following instances to be defined by the language module: |
| | 468 | * |
| | 469 | * standardImpCtx: this is the standard context, which indicates that we |
| | 470 | * want the default format for the implicit action announcement. |
| | 471 | * |
| | 472 | * tryingImpCtx: this is the "trying" context, which indicates that we |
| | 473 | * want the announcement to phrase the action to indicate that we're only |
| | 474 | * trying the action, not actually performing it. We use this when the |
| | 475 | * implicit action has failed, in which case we want our announcement to |
| | 476 | * say that we're merely attempting the action; the announcement |
| | 477 | * shouldn't imply that the action has actually been performed. |
| | 478 | * |
| | 479 | * askingImpCtx: this is the "asking" context, which indicates that the |
| | 480 | * action was interrupted with an interactive question, such as a prompt |
| | 481 | * for a missing direct object. |
| | 482 | * |
| | 483 | * We leave it up to the language module to define the class and these |
| | 484 | * two instances. This lets the language module represent the context |
| | 485 | * types any way it likes. |
| | 486 | */ |
| | 487 | |
| | 488 | /* |
| | 489 | * Implicit action announcement. This is displayed when we perform a |
| | 490 | * command implicitly, which we usually do to fulfill a precondition of |
| | 491 | * an action. |
| | 492 | * |
| | 493 | * In English, we usually show an implied action as the verb participle |
| | 494 | * phrase ("opening the door"), prefixed with "first", and enclosed in |
| | 495 | * parentheses on a line by itself (hence, "(first opening the door)"). |
| | 496 | */ |
| | 497 | class ImplicitActionAnnouncement: CommandAnnouncement |
| | 498 | construct(action, msg) |
| | 499 | { |
| | 500 | /* use the given message property */ |
| | 501 | messageProp_ = msg; |
| | 502 | |
| | 503 | /* |
| | 504 | * Inherit default. The first message parameter is the action; |
| | 505 | * the second is our standard implicit action context object, |
| | 506 | * indicating that we want the normal context. |
| | 507 | */ |
| | 508 | inherited(action, standardImpCtx); |
| | 509 | } |
| | 510 | |
| | 511 | /* |
| | 512 | * Make this announcement silent. This eliminates any announcement |
| | 513 | * for this action, but makes it otherwise behave like a normal |
| | 514 | * implied action. |
| | 515 | */ |
| | 516 | makeSilent() |
| | 517 | { |
| | 518 | /* clear my message text */ |
| | 519 | messageText_ = ''; |
| | 520 | |
| | 521 | /* |
| | 522 | * use the silent announcement message if we have to regenerate |
| | 523 | * our text for another context |
| | 524 | */ |
| | 525 | messageProp_ = &silentImplicitAction; |
| | 526 | } |
| | 527 | |
| | 528 | /* |
| | 529 | * Note that the action we're attempting is merely an attempt that |
| | 530 | * failed. This will change our report to indicate that we're only |
| | 531 | * trying the action, rather than suggesting that we actually carried |
| | 532 | * it out. |
| | 533 | */ |
| | 534 | noteJustTrying() |
| | 535 | { |
| | 536 | /* note that we're just trying the action */ |
| | 537 | justTrying = true; |
| | 538 | |
| | 539 | /* change our message to the "trying" form */ |
| | 540 | messageText_ = getMessageText(getAction(), tryingImpCtx); |
| | 541 | } |
| | 542 | |
| | 543 | /* |
| | 544 | * Note that the action we're attempting is incomplete, as it was |
| | 545 | * interupted for interactive input (such as asking for a missing |
| | 546 | * object). |
| | 547 | */ |
| | 548 | noteQuestion() |
| | 549 | { |
| | 550 | /* note that the action was interrupted with a question */ |
| | 551 | justAsking = true; |
| | 552 | |
| | 553 | /* change our message to the "asking" form */ |
| | 554 | messageText_ = getMessageText(getAction(), askingImpCtx); |
| | 555 | } |
| | 556 | |
| | 557 | /* |
| | 558 | * Flag: we're just attempting the action; this is set when we |
| | 559 | * determine that the implicit action has failed, in which case we |
| | 560 | * want an announcement indicating that we're merely attempting the |
| | 561 | * action, not actually performing it. Presume that we're actually |
| | 562 | * going to perform the action; the action can change this if |
| | 563 | * necessary. |
| | 564 | */ |
| | 565 | justTrying = nil |
| | 566 | |
| | 567 | /* flag: the action was interrupted with an interactive question */ |
| | 568 | justAsking = nil |
| | 569 | ; |
| | 570 | |
| | 571 | class CommandSepAnnouncement: CommandAnnouncement |
| | 572 | construct() |
| | 573 | { |
| | 574 | /* we're not associated with an iteration or action */ |
| | 575 | action_ = nil; |
| | 576 | iter_ = 0; |
| | 577 | } |
| | 578 | |
| | 579 | showMessage() |
| | 580 | { |
| | 581 | /* show a command separator */ |
| | 582 | "<.commandsep>"; |
| | 583 | } |
| | 584 | ; |
| | 585 | |
| | 586 | /* ------------------------------------------------------------------------ */ |
| | 587 | /* |
| | 588 | * Command Transcript. This is a "semantic transcript" of the results of |
| | 589 | * a command. This provides a list of CommandReport objects describing |
| | 590 | * the results of the command. |
| | 591 | */ |
| | 592 | class CommandTranscript: OutputFilter |
| | 593 | construct() |
| | 594 | { |
| | 595 | /* set up a vector to hold the reports */ |
| | 596 | reports_ = new Vector(5); |
| | 597 | } |
| | 598 | |
| | 599 | /* |
| | 600 | * flag: the command has failed (i.e., at least one failure report |
| | 601 | * has been generated) |
| | 602 | */ |
| | 603 | isFailure = nil |
| | 604 | |
| | 605 | /* |
| | 606 | * Note that the current action has failed. This is equivalent to |
| | 607 | * adding a reportFailure() message to the transcript. |
| | 608 | */ |
| | 609 | noteFailure() |
| | 610 | { |
| | 611 | /* add an empty reportFailure message */ |
| | 612 | reportFailure(''); |
| | 613 | } |
| | 614 | |
| | 615 | /* |
| | 616 | * Did the given action fail? This scans the transcript to determine |
| | 617 | * if there are any failure messages associated with the given |
| | 618 | * action. |
| | 619 | */ |
| | 620 | actionFailed(action) |
| | 621 | { |
| | 622 | /* |
| | 623 | * scan the transcript for failure messages that are associated |
| | 624 | * with the given action |
| | 625 | */ |
| | 626 | return reports_.indexWhich( |
| | 627 | {x: x.isPartOf(action) && x.isFailure}) != nil; |
| | 628 | } |
| | 629 | |
| | 630 | /* |
| | 631 | * flag: I'm active; when this is nil, we'll pass text through our |
| | 632 | * filter routine unchanged |
| | 633 | */ |
| | 634 | isActive = true |
| | 635 | |
| | 636 | /* |
| | 637 | * Summarize the current action's reports. This allows a caller to |
| | 638 | * turn a series of iterated reports into a single report for the |
| | 639 | * entire action. For example, we could change something like this: |
| | 640 | * |
| | 641 | * gold coin: Bob accepts the gold coin. |
| | 642 | *. gold coin: Bob accepts the gold coin. |
| | 643 | *. gold coin: Bob accepts the gold coin. |
| | 644 | * |
| | 645 | * into this: |
| | 646 | * |
| | 647 | * Bob accepts the three gold coins. |
| | 648 | * |
| | 649 | * This function runs through the reports for the current action, |
| | 650 | * submitting each one to the 'cond' callback to see if it's of |
| | 651 | * interest to the summary. For each consecutive run of two or more |
| | 652 | * reports that can be summarized, we'll remove the reports that |
| | 653 | * 'cond' accepted, and we'll remove the multiple-object announcement |
| | 654 | * reports associated with them, and we'll insert a new report with |
| | 655 | * the message returned by the 'report' callback. |
| | 656 | * |
| | 657 | * 'cond' is called as cond(x), where 'x' is a report object. This |
| | 658 | * callback returns true if the report can be summarized for the |
| | 659 | * caller's purposes, nil if not. |
| | 660 | * |
| | 661 | * 'report' is called as report(vec), where 'vec' is a Vector |
| | 662 | * consisting of all of the consecutive report objects that we're now |
| | 663 | * summarizing. This function returns a string giving the message to |
| | 664 | * use in place of the reports we're removing. This should be a |
| | 665 | * summary message, standing in for the set of individual reports |
| | 666 | * we're removing. |
| | 667 | * |
| | 668 | * There's an important subtlety to note. If the messages you're |
| | 669 | * summarizing are conversational (that is, if they're generated by |
| | 670 | * TopicEntry responses), you should take care to generate the full |
| | 671 | * replacement text in the 'report' part, rather than doing so in |
| | 672 | * separate code that you run after summarizeAction() returns. This |
| | 673 | * is important because it ensures that the Conversation Manager |
| | 674 | * knows that your replacement message is part of the same |
| | 675 | * conversation. If you wait until after summarizeAction() returns |
| | 676 | * to generate more response text, the conversation manager won't |
| | 677 | * realize that the additional text is part of the same conversation. |
| | 678 | */ |
| | 679 | summarizeAction(cond, report) |
| | 680 | { |
| | 681 | local vec = new Vector(8); |
| | 682 | local rpt = reports_; |
| | 683 | local cnt = rpt.length(); |
| | 684 | local i; |
| | 685 | |
| | 686 | /* find the first report for the current action */ |
| | 687 | for (i = 1 ; i <= cnt && rpt[i].getAction() != gAction ; ++i) ; |
| | 688 | |
| | 689 | /* iterate over the transcript for the current action */ |
| | 690 | for ( ; ; ++i) |
| | 691 | { |
| | 692 | local ok; |
| | 693 | |
| | 694 | /* presume we won't find a summarizable item */ |
| | 695 | ok = nil; |
| | 696 | |
| | 697 | /* if we're still in range, check what we have */ |
| | 698 | if (i <= cnt) |
| | 699 | { |
| | 700 | /* get the current item */ |
| | 701 | local cur = rpt[i]; |
| | 702 | |
| | 703 | /* if this one is of interest, note it in the vector */ |
| | 704 | if (cond(cur)) |
| | 705 | { |
| | 706 | /* add it to our vector of summarizable reports */ |
| | 707 | vec.append(cur); |
| | 708 | |
| | 709 | /* note that we're okay on this round */ |
| | 710 | ok = true; |
| | 711 | } |
| | 712 | else if (cur.ofKind(ImplicitActionAnnouncement) |
| | 713 | || cur.ofKind(MultiObjectAnnouncement) |
| | 714 | || cur.ofKind(DefaultCommandReport) |
| | 715 | || cur.ofKind(ConvBoundaryReport)) |
| | 716 | { |
| | 717 | /* we can keep these in summaries */ |
| | 718 | ok = true; |
| | 719 | } |
| | 720 | } |
| | 721 | |
| | 722 | /* |
| | 723 | * If this item isn't summarizable, or we've reached the last |
| | 724 | * item, generate a summary of any we have so far. (We need |
| | 725 | * to generate the summary on reaching the last item because |
| | 726 | * there are no further items that could go in the summary.) |
| | 727 | */ |
| | 728 | if (!ok || i == cnt) |
| | 729 | { |
| | 730 | /* if we have two or more items, generate a summary */ |
| | 731 | if (vec.length() > 1) |
| | 732 | { |
| | 733 | local insIdx; |
| | 734 | local txt; |
| | 735 | |
| | 736 | /* remove each item in the vector */ |
| | 737 | foreach (local cur in vec) |
| | 738 | { |
| | 739 | local idx; |
| | 740 | |
| | 741 | /* get the index of the current item */ |
| | 742 | idx = rpt.indexOf(cur); |
| | 743 | |
| | 744 | /* |
| | 745 | * we're summarizing this item, so remove the |
| | 746 | * individual item - subsume it into the summary |
| | 747 | */ |
| | 748 | rpt.removeElementAt(idx); |
| | 749 | --i; |
| | 750 | --cnt; |
| | 751 | |
| | 752 | /* insert the summary here */ |
| | 753 | insIdx = idx; |
| | 754 | |
| | 755 | /* |
| | 756 | * skip any implicit action announcements, |
| | 757 | * default command announcements, and |
| | 758 | * conversational boundary markers |
| | 759 | */ |
| | 760 | for (--idx ; |
| | 761 | idx > 0 |
| | 762 | && (rpt[idx].ofKind(ImplicitActionAnnouncement) |
| | 763 | || rpt[idx].ofKind(DefaultCommandReport) |
| | 764 | || rpt[idx].ofKind(ConvBoundaryReport)) ; |
| | 765 | --idx) ; |
| | 766 | |
| | 767 | /* |
| | 768 | * if the preceding element is a multi-object |
| | 769 | * announcement, remove it - let the summary |
| | 770 | * mention the individual objects if it wants to |
| | 771 | */ |
| | 772 | if (idx > 0 |
| | 773 | && rpt[idx].ofKind(MultiObjectAnnouncement)) |
| | 774 | { |
| | 775 | /* remove this element and adjust the counters */ |
| | 776 | rpt.removeElementAt(idx); |
| | 777 | --i; |
| | 778 | --cnt; |
| | 779 | --insIdx; |
| | 780 | |
| | 781 | /* |
| | 782 | * If this leaves us with a <.convbegin> |
| | 783 | * preceded directly by a <.convend> for the |
| | 784 | * same actor, the two cancel each other out. |
| | 785 | * Simply remove both of them. |
| | 786 | */ |
| | 787 | if (idx <= rpt.length() |
| | 788 | && idx > 1 |
| | 789 | && rpt[idx].ofKind(ConvBeginReport) |
| | 790 | && rpt[idx-1].ofKind(ConvEndReport) |
| | 791 | && rpt[idx].actorID == rpt[idx-1].actorID) |
| | 792 | { |
| | 793 | /* |
| | 794 | * we have a canceling <.convend> + |
| | 795 | * <.convbegin> pair - simply remove them |
| | 796 | * both and adjust the counters |
| | 797 | * accordingly |
| | 798 | */ |
| | 799 | rpt.removeRange(idx - 1, idx); |
| | 800 | i -= 2; |
| | 801 | cnt -= 2; |
| | 802 | insIdx -= 2; |
| | 803 | } |
| | 804 | } |
| | 805 | } |
| | 806 | |
| | 807 | /* ask the caller for the summary */ |
| | 808 | txt = report(vec); |
| | 809 | |
| | 810 | /* insert it */ |
| | 811 | rpt.insertAt(insIdx, new MainCommandReport(txt)); |
| | 812 | ++cnt; |
| | 813 | ++i; |
| | 814 | } |
| | 815 | |
| | 816 | /* clear the vector */ |
| | 817 | if (vec.length() > 0) |
| | 818 | vec.removeRange(1, vec.length()); |
| | 819 | } |
| | 820 | |
| | 821 | /* if we've reached the end of the list, we're done */ |
| | 822 | if (i > cnt) |
| | 823 | break; |
| | 824 | } |
| | 825 | } |
| | 826 | |
| | 827 | /* activate - set up to capture output */ |
| | 828 | activate() |
| | 829 | { |
| | 830 | /* make myself active */ |
| | 831 | isActive = true; |
| | 832 | } |
| | 833 | |
| | 834 | /* deactivate - stop capturing output */ |
| | 835 | deactivate() |
| | 836 | { |
| | 837 | /* make myself inactive */ |
| | 838 | isActive = nil; |
| | 839 | } |
| | 840 | |
| | 841 | /* |
| | 842 | * Count an iteration. An Action should call this once per iteration |
| | 843 | * if it's a top-level (non-nested) command. |
| | 844 | */ |
| | 845 | newIter() { ++iter_; } |
| | 846 | |
| | 847 | /* |
| | 848 | * Flush the transcript in preparation for reading input. This shows |
| | 849 | * all pending reports, clears the backlog of reports (so that we |
| | 850 | * don't show them again in the future), and deactivates the |
| | 851 | * transcript's capture feature so that subsequent output goes |
| | 852 | * directly to the output stream. |
| | 853 | * |
| | 854 | * We return the former activation status - that is, we return true |
| | 855 | * if the transcript was activated before the call, nil if not. |
| | 856 | */ |
| | 857 | flushForInput() |
| | 858 | { |
| | 859 | /* show our reports, and deactivate output capture */ |
| | 860 | local wasActive = showReports(true); |
| | 861 | |
| | 862 | /* clear the reports, since we've now shown them all */ |
| | 863 | clearReports(); |
| | 864 | |
| | 865 | /* return the previous activation status */ |
| | 866 | return wasActive; |
| | 867 | } |
| | 868 | |
| | 869 | /* |
| | 870 | * Show our reports. Returns true if the transcript was previously |
| | 871 | * active, nil if not. |
| | 872 | */ |
| | 873 | showReports(deact) |
| | 874 | { |
| | 875 | local wasActive; |
| | 876 | |
| | 877 | /* |
| | 878 | * remember whether we were active or not originally, then |
| | 879 | * deactivate (maybe just temporarily) so that we can write out |
| | 880 | * our reports without recursively intercepting them |
| | 881 | */ |
| | 882 | wasActive = isActive; |
| | 883 | deactivate(); |
| | 884 | |
| | 885 | /* first, apply all defined transformations to our transcript */ |
| | 886 | applyTransforms(); |
| | 887 | |
| | 888 | /* |
| | 889 | * Temporarily cancel any sense context message blocking. We |
| | 890 | * have already taken into account for each report whether or |
| | 891 | * not the report was visible when it was generated, so we can |
| | 892 | * display each report that made it past that check without any |
| | 893 | * further conditions. |
| | 894 | */ |
| | 895 | callWithSenseContext(nil, nil, new function() |
| | 896 | { |
| | 897 | /* show the reports */ |
| | 898 | foreach (local cur in reports_) |
| | 899 | { |
| | 900 | /* if we're allowed to show this report, show it */ |
| | 901 | if (canShowReport(cur)) |
| | 902 | cur.showMessage(); |
| | 903 | } |
| | 904 | }); |
| | 905 | |
| | 906 | /* |
| | 907 | * if we were active and we're not being asked to deactivate, |
| | 908 | * re-activate now that we're finished showing our reports |
| | 909 | */ |
| | 910 | if (wasActive && !deact) |
| | 911 | activate(); |
| | 912 | |
| | 913 | /* return the former activation status */ |
| | 914 | return wasActive; |
| | 915 | } |
| | 916 | |
| | 917 | /* |
| | 918 | * Add a report. |
| | 919 | */ |
| | 920 | addReport(report) |
| | 921 | { |
| | 922 | /* check for a failure report */ |
| | 923 | if (report.isFailure) |
| | 924 | { |
| | 925 | /* set the failure flag for the entire command */ |
| | 926 | isFailure = true; |
| | 927 | |
| | 928 | /* |
| | 929 | * If this is an implied command, and the actor is in "NPC |
| | 930 | * mode", suppress the report. When an implied action fails |
| | 931 | * in NPC mode, we act as though we never attempted the |
| | 932 | * action. |
| | 933 | */ |
| | 934 | if (gAction.isImplicit && gActor.impliedCommandMode() == ModeNPC) |
| | 935 | { |
| | 936 | /* add a failure marker, not the message report */ |
| | 937 | reports_.append(new FailCommandMarker()); |
| | 938 | |
| | 939 | /* that's all we need to add */ |
| | 940 | return; |
| | 941 | } |
| | 942 | } |
| | 943 | |
| | 944 | /* |
| | 945 | * Do not queue reports made while the sense context is blocking |
| | 946 | * output because the player character cannot sense the locus of |
| | 947 | * the action. Note that this check comes before we queue the |
| | 948 | * report, but after we've noted any effect on the status of the |
| | 949 | * overall action; even if we're not going to show the report, |
| | 950 | * its status effects are still valid. |
| | 951 | */ |
| | 952 | if (senseContext.isBlocking) |
| | 953 | return; |
| | 954 | |
| | 955 | /* |
| | 956 | * If the new report's iteration ID hasn't been set already, note |
| | 957 | * the current iteration in the report. Some types of reports |
| | 958 | * will have already set a specific iteration before we get here, |
| | 959 | * so set the iteration ID only if the report hasn't done so |
| | 960 | * already. |
| | 961 | */ |
| | 962 | if (report.iter_ == nil) |
| | 963 | report.iter_ = iter_; |
| | 964 | |
| | 965 | /* append the report */ |
| | 966 | reports_.append(report); |
| | 967 | } |
| | 968 | |
| | 969 | /* get the last report added */ |
| | 970 | getLastReport() |
| | 971 | { |
| | 972 | local cnt = reports_.length(); |
| | 973 | return (cnt == 0 ? nil : reports_[cnt]); |
| | 974 | } |
| | 975 | |
| | 976 | /* delete the last report added */ |
| | 977 | deleteLastReport() |
| | 978 | { |
| | 979 | local cnt = reports_.length(); |
| | 980 | if (cnt != 0) |
| | 981 | reports_.removeElementAt(cnt); |
| | 982 | } |
| | 983 | |
| | 984 | /* |
| | 985 | * Add a marker report. This adds a marker to the report stream, |
| | 986 | * and returns the marker object. The marker doesn't show any |
| | 987 | * message in the final display, but callers can use a pair of |
| | 988 | * markers to identify a range of reports for later reordering or |
| | 989 | * removal. |
| | 990 | */ |
| | 991 | addMarker() |
| | 992 | { |
| | 993 | /* create the new report */ |
| | 994 | local marker = new MarkerReport(); |
| | 995 | |
| | 996 | /* add it to the stream */ |
| | 997 | addReport(marker); |
| | 998 | |
| | 999 | /* return the new report */ |
| | 1000 | return marker; |
| | 1001 | } |
| | 1002 | |
| | 1003 | /* delete the reports between two markers */ |
| | 1004 | deleteRange(marker1, marker2) |
| | 1005 | { |
| | 1006 | local idx1, idx2; |
| | 1007 | |
| | 1008 | /* find the indices of the two markers */ |
| | 1009 | idx1 = reports_.indexOf(marker1); |
| | 1010 | idx2 = reports_.indexOf(marker2); |
| | 1011 | |
| | 1012 | /* if we found both, delete the range */ |
| | 1013 | if (idx1 != nil && idx2 != nil) |
| | 1014 | reports_.removeRange(idx1, idx2); |
| | 1015 | } |
| | 1016 | |
| | 1017 | /* |
| | 1018 | * Pull out the reports between two markers, and reinsert them at |
| | 1019 | * the end of the transcript. |
| | 1020 | */ |
| | 1021 | moveRangeAppend(marker1, marker2) |
| | 1022 | { |
| | 1023 | local idx1, idx2; |
| | 1024 | |
| | 1025 | /* find the indices of the two markers */ |
| | 1026 | idx1 = reports_.indexOf(marker1); |
| | 1027 | idx2 = reports_.indexOf(marker2); |
| | 1028 | |
| | 1029 | /* if we didn't find both, ignore the request */ |
| | 1030 | if (idx1 == nil || idx2 == nil) |
| | 1031 | return; |
| | 1032 | |
| | 1033 | /* append each item in the range to the end of the report list */ |
| | 1034 | for (local i = idx1 ; i <= idx2 ; ++i) |
| | 1035 | reports_.append(reports_[i]); |
| | 1036 | |
| | 1037 | /* delete the original copies */ |
| | 1038 | reports_.removeRange(idx1, idx2); |
| | 1039 | } |
| | 1040 | |
| | 1041 | /* |
| | 1042 | * Perform a callback on all of the reports in the transcript. |
| | 1043 | * We'll invoke the given callback function func(rpt) once for each |
| | 1044 | * report, with the report object as the parameter. |
| | 1045 | */ |
| | 1046 | forEachReport(func) { reports_.forEach(func); } |
| | 1047 | |
| | 1048 | /* |
| | 1049 | * End the description section of the report. This adds a marker |
| | 1050 | * report that indicates that anything following (and part of the |
| | 1051 | * same action) is no longer part of the description; this can be |
| | 1052 | * important when we apply the default description suppression |
| | 1053 | * transformation, because it tells us not to consider the |
| | 1054 | * non-descriptive messages following this marker when, for example, |
| | 1055 | * suppressing default descriptive messages. |
| | 1056 | */ |
| | 1057 | endDescription() |
| | 1058 | { |
| | 1059 | /* add an end-of-description report */ |
| | 1060 | addReport(new EndOfDescReport()); |
| | 1061 | } |
| | 1062 | |
| | 1063 | /* |
| | 1064 | * Announce that the action is implicit |
| | 1065 | */ |
| | 1066 | announceImplicit(action, msgProp) |
| | 1067 | { |
| | 1068 | /* |
| | 1069 | * If the actor performing the command is not in "player" mode, |
| | 1070 | * save an implicit action announcement; for NPC mode, we treat |
| | 1071 | * implicit command results like any other results, so we don't |
| | 1072 | * want a separate announcement. |
| | 1073 | */ |
| | 1074 | if (gActor.impliedCommandMode() == ModePlayer) |
| | 1075 | { |
| | 1076 | /* create the new report */ |
| | 1077 | local report = new ImplicitActionAnnouncement(action, msgProp); |
| | 1078 | |
| | 1079 | /* add it to the transcript */ |
| | 1080 | addReport(report); |
| | 1081 | |
| | 1082 | /* return it */ |
| | 1083 | return report; |
| | 1084 | } |
| | 1085 | else |
| | 1086 | { |
| | 1087 | /* no need for a report */ |
| | 1088 | return nil; |
| | 1089 | } |
| | 1090 | } |
| | 1091 | |
| | 1092 | /* |
| | 1093 | * Announce a remapped action |
| | 1094 | */ |
| | 1095 | announceRemappedAction() |
| | 1096 | { |
| | 1097 | /* save a remapped-action announcement */ |
| | 1098 | addReport(new RemappedActionAnnouncement()); |
| | 1099 | } |
| | 1100 | |
| | 1101 | /* |
| | 1102 | * Announce one of a set of objects to a multi-object action. We'll |
| | 1103 | * record this announcement for display with our report list. |
| | 1104 | */ |
| | 1105 | announceMultiActionObject(obj, whichObj) |
| | 1106 | { |
| | 1107 | /* save a multi-action object announcement */ |
| | 1108 | addReport(new MultiObjectAnnouncement(obj, whichObj, gAction)); |
| | 1109 | } |
| | 1110 | |
| | 1111 | /* |
| | 1112 | * Announce an object that was resolved with slight ambiguity. |
| | 1113 | */ |
| | 1114 | announceAmbigActionObject(obj, whichObj) |
| | 1115 | { |
| | 1116 | /* save an ambiguous object announcement */ |
| | 1117 | addReport(new AmbigObjectAnnouncement(obj, whichObj, gAction)); |
| | 1118 | } |
| | 1119 | |
| | 1120 | /* |
| | 1121 | * Announce a default object. |
| | 1122 | */ |
| | 1123 | announceDefaultObject(obj, whichObj, action, allResolved) |
| | 1124 | { |
| | 1125 | /* save the default object announcement */ |
| | 1126 | addReport(new DefaultObjectAnnouncement( |
| | 1127 | obj, whichObj, action, allResolved)); |
| | 1128 | } |
| | 1129 | |
| | 1130 | /* |
| | 1131 | * Add a command separator. |
| | 1132 | */ |
| | 1133 | addCommandSep() |
| | 1134 | { |
| | 1135 | /* add a command separator announcement */ |
| | 1136 | addReport(new CommandSepAnnouncement()); |
| | 1137 | } |
| | 1138 | |
| | 1139 | /* |
| | 1140 | * clear our reports |
| | 1141 | */ |
| | 1142 | clearReports() |
| | 1143 | { |
| | 1144 | /* forget all of the reports in the main list */ |
| | 1145 | if (reports_.length() != 0) |
| | 1146 | reports_.removeRange(1, reports_.length()); |
| | 1147 | } |
| | 1148 | |
| | 1149 | /* |
| | 1150 | * Can we show a given report? By default, we always return true, |
| | 1151 | * but subclasses might want to override this to suppress certain |
| | 1152 | * types of reports. |
| | 1153 | */ |
| | 1154 | canShowReport(report) { return true; } |
| | 1155 | |
| | 1156 | /* |
| | 1157 | * Filter text. If we're active, we'll turn the text into a command |
| | 1158 | * report and add it to our report list, blocking the text from |
| | 1159 | * reaching the underlying stream; otherwise, we'll pass it through |
| | 1160 | * unchanged. |
| | 1161 | */ |
| | 1162 | filterText(ostr, txt) |
| | 1163 | { |
| | 1164 | /* if we're inactive, pass text through unchanged */ |
| | 1165 | if (!isActive) |
| | 1166 | return txt; |
| | 1167 | |
| | 1168 | /* |
| | 1169 | * If the current sense context doesn't allow any messages to be |
| | 1170 | * generated, block the generated text entirely. We want to |
| | 1171 | * block text or not according to the sense context in effect |
| | 1172 | * now; so we must note it now rather than wait until we |
| | 1173 | * actually display the report, since the context could be |
| | 1174 | * different by then. |
| | 1175 | */ |
| | 1176 | if (senseContext.isBlocking) |
| | 1177 | return nil; |
| | 1178 | |
| | 1179 | /* add a main report to our list if the text is non-empty */ |
| | 1180 | if (txt != '') |
| | 1181 | addReport(new MainCommandReport(txt)); |
| | 1182 | |
| | 1183 | /* capture the text - send nothing to the underlying stream */ |
| | 1184 | return nil; |
| | 1185 | } |
| | 1186 | |
| | 1187 | /* apply transformations */ |
| | 1188 | applyTransforms() |
| | 1189 | { |
| | 1190 | /* apply each defined transformation */ |
| | 1191 | foreach (local cur in transforms_) |
| | 1192 | cur.applyTransform(self, reports_); |
| | 1193 | } |
| | 1194 | |
| | 1195 | /* |
| | 1196 | * check to see if the current action has a report matching the given |
| | 1197 | * criteria |
| | 1198 | */ |
| | 1199 | currentActionHasReport(func) |
| | 1200 | { |
| | 1201 | /* check to see if we can find a matching report */ |
| | 1202 | return (findCurrentActionReport(func) != nil); |
| | 1203 | } |
| | 1204 | |
| | 1205 | /* find a report in the current action that matches the given criteria */ |
| | 1206 | findCurrentActionReport(func) |
| | 1207 | { |
| | 1208 | /* |
| | 1209 | * Find an action that's part of the current iteration and which |
| | 1210 | * matches the given function's criteria. Return the first match |
| | 1211 | * we find. |
| | 1212 | */ |
| | 1213 | return reports_.valWhich({x: x.iter_ == iter_ && (func)(x)}); |
| | 1214 | } |
| | 1215 | |
| | 1216 | /* |
| | 1217 | * iteration number - for an iterated top-level command, this helps |
| | 1218 | * us keep the results for a particular iteration grouped together |
| | 1219 | */ |
| | 1220 | iter_ = 1 |
| | 1221 | |
| | 1222 | /* our vector of reports */ |
| | 1223 | reports_ = nil |
| | 1224 | |
| | 1225 | /* our list of transformations */ |
| | 1226 | transforms_ = [defaultReportTransform, implicitGroupTransform, |
| | 1227 | reportOrderTransform, complexMultiTransform] |
| | 1228 | ; |
| | 1229 | |
| | 1230 | /* ------------------------------------------------------------------------ */ |
| | 1231 | /* |
| | 1232 | * Transcript Transform. |
| | 1233 | */ |
| | 1234 | class TranscriptTransform: object |
| | 1235 | /* |
| | 1236 | * Apply our transform to the transcript vector. By default, we do |
| | 1237 | * nothing; each subclass must override this to manipulate the vector |
| | 1238 | * to make the change it wants to make. |
| | 1239 | */ |
| | 1240 | applyTransform(trans, vec) { } |
| | 1241 | ; |
| | 1242 | |
| | 1243 | /* ------------------------------------------------------------------------ */ |
| | 1244 | /* |
| | 1245 | * Transcript Transform: set before/main/after report order. We'll look |
| | 1246 | * for any before/after reports that are out of order with respect to |
| | 1247 | * their main reports, and move them into the appropriate positions. |
| | 1248 | */ |
| | 1249 | reportOrderTransform: TranscriptTransform |
| | 1250 | applyTransform(trans, vec) |
| | 1251 | { |
| | 1252 | /* scan for before/after reports */ |
| | 1253 | for (local i = 1, local len = vec.length() ; i <= len ; ++i) |
| | 1254 | { |
| | 1255 | /* get this item */ |
| | 1256 | local cur = vec[i]; |
| | 1257 | |
| | 1258 | /* if this is a before/after report, consider moving it */ |
| | 1259 | if (cur.ofKind(FullCommandReport) && cur.seqNum != nil) |
| | 1260 | { |
| | 1261 | local idx; |
| | 1262 | |
| | 1263 | /* |
| | 1264 | * This item cares about its sequencing, so it could be |
| | 1265 | * out of order with respect to other items from the same |
| | 1266 | * sequence. Find the first item with a higher sequence |
| | 1267 | * number from the same group, and make sure this item is |
| | 1268 | * before the first such item. |
| | 1269 | */ |
| | 1270 | for (idx = 1 ; idx < i ; ++idx) |
| | 1271 | { |
| | 1272 | local x; |
| | 1273 | |
| | 1274 | /* get this item */ |
| | 1275 | x = vec[idx]; |
| | 1276 | |
| | 1277 | /* if x should come after cur, we need to move cur */ |
| | 1278 | if (x.ofKind(FullCommandReport) |
| | 1279 | && x.seqNum > cur.seqNum |
| | 1280 | && x.isPartOf(cur)) |
| | 1281 | { |
| | 1282 | /* remove cur and reinsert it before x */ |
| | 1283 | vec.removeElementAt(i); |
| | 1284 | vec.insertAt(idx, cur); |
| | 1285 | |
| | 1286 | /* adjust our scan index for the removal */ |
| | 1287 | --i; |
| | 1288 | |
| | 1289 | /* no need to look any further */ |
| | 1290 | break; |
| | 1291 | } |
| | 1292 | } |
| | 1293 | } |
| | 1294 | } |
| | 1295 | } |
| | 1296 | ; |
| | 1297 | |
| | 1298 | /* ------------------------------------------------------------------------ */ |
| | 1299 | /* |
| | 1300 | * Transcript Transform: remove unnecessary default reports. We'll scan |
| | 1301 | * the transcript for default reports for actions which also have |
| | 1302 | * implicit announcements or non-default reports, and remove those |
| | 1303 | * default reports. We'll also remove default descriptive reports which |
| | 1304 | * also have non-default reports in the same action. |
| | 1305 | */ |
| | 1306 | defaultReportTransform: TranscriptTransform |
| | 1307 | applyTransform(trans, vec) |
| | 1308 | { |
| | 1309 | /* scan for default reports */ |
| | 1310 | for (local i = 1, local len = vec.length() ; i <= len ; ++i) |
| | 1311 | { |
| | 1312 | local cur; |
| | 1313 | |
| | 1314 | /* get this item */ |
| | 1315 | cur = vec[i]; |
| | 1316 | |
| | 1317 | /* |
| | 1318 | * if this is a default report, check to see if we want to |
| | 1319 | * keep it |
| | 1320 | */ |
| | 1321 | if (cur.ofKind(DefaultCommandReport)) |
| | 1322 | { |
| | 1323 | /* |
| | 1324 | * check for a main report or an implicit announcement |
| | 1325 | * associated with the same action; if we find anything, |
| | 1326 | * we don't need to keep the default report |
| | 1327 | */ |
| | 1328 | if (vec.indexWhich( |
| | 1329 | {x: (x != cur |
| | 1330 | && cur.isPartOf(x) |
| | 1331 | && (x.ofKind(FullCommandReport) |
| | 1332 | || x.ofKind(ImplicitActionAnnouncement))) |
| | 1333 | }) != nil) |
| | 1334 | { |
| | 1335 | /* we don't need this default report */ |
| | 1336 | vec.removeElementAt(i); |
| | 1337 | |
| | 1338 | /* adjust our scan index for the removal */ |
| | 1339 | --i; |
| | 1340 | --len; |
| | 1341 | } |
| | 1342 | } |
| | 1343 | |
| | 1344 | /* |
| | 1345 | * if this is a default descriptive report, check to see if |
| | 1346 | * we want to keep it |
| | 1347 | */ |
| | 1348 | if (cur.ofKind(DefaultDescCommandReport)) |
| | 1349 | { |
| | 1350 | local fullIdx; |
| | 1351 | |
| | 1352 | /* |
| | 1353 | * check for a main report associated with the same |
| | 1354 | * action |
| | 1355 | */ |
| | 1356 | fullIdx = vec.indexWhich( |
| | 1357 | {x: (x != cur |
| | 1358 | && cur.isPartOf(x) |
| | 1359 | && x.ofKind(FullCommandReport))}); |
| | 1360 | |
| | 1361 | /* |
| | 1362 | * if we found another report, check to see if it comes |
| | 1363 | * before or after any 'end of description' for the same |
| | 1364 | * action |
| | 1365 | */ |
| | 1366 | if (fullIdx != nil) |
| | 1367 | { |
| | 1368 | local endIdx; |
| | 1369 | |
| | 1370 | /* find the 'end of description' report, if any */ |
| | 1371 | endIdx = vec.indexWhich( |
| | 1372 | {x: (x != cur |
| | 1373 | && cur.isPartOf(x) |
| | 1374 | && x.ofKind(EndOfDescReport))}); |
| | 1375 | |
| | 1376 | /* |
| | 1377 | * if we found a full report before the |
| | 1378 | * end-of-description report, then the full report is |
| | 1379 | * part of the description and thus should suppress |
| | 1380 | * the default report; otherwise, the description |
| | 1381 | * portion includes only the default report and the |
| | 1382 | * default report should thus remain |
| | 1383 | */ |
| | 1384 | if (endIdx == nil || fullIdx < endIdx) |
| | 1385 | { |
| | 1386 | /* don't keep the default descriptive report */ |
| | 1387 | vec.removeElementAt(i); |
| | 1388 | |
| | 1389 | /* adjust our indices for the removal */ |
| | 1390 | --i; |
| | 1391 | --len; |
| | 1392 | } |
| | 1393 | } |
| | 1394 | } |
| | 1395 | } |
| | 1396 | } |
| | 1397 | ; |
| | 1398 | |
| | 1399 | /* ------------------------------------------------------------------------ */ |
| | 1400 | /* |
| | 1401 | * Transcript Transform: group implicit announcements. We'll find any |
| | 1402 | * runs of consecutive implicit command announcements, and group each run |
| | 1403 | * into a single announcement listing all of the implied actions. For |
| | 1404 | * example, we'll turn this: |
| | 1405 | * |
| | 1406 | *. >go south |
| | 1407 | *. (first opening the door) |
| | 1408 | *. (first unlocking the door) |
| | 1409 | * |
| | 1410 | * this into: |
| | 1411 | * |
| | 1412 | *. >go south |
| | 1413 | *. (first opening the door and unlocking the door) |
| | 1414 | * |
| | 1415 | * In addition, if we find an implicit announcement in the middle of a |
| | 1416 | * set of regular command reports, and it's for an action nested within |
| | 1417 | * the action generating the regular reports, we'll start a new paragraph |
| | 1418 | * before the implicit announcement. |
| | 1419 | */ |
| | 1420 | implicitGroupTransform: TranscriptTransform |
| | 1421 | applyTransform(trans, vec) |
| | 1422 | { |
| | 1423 | /* |
| | 1424 | * Scan for implicit announcements whose actions failed, and mark |
| | 1425 | * the implicit actions as such. This allows us to phrase the |
| | 1426 | * implicit announcements as attempts rather than as actual |
| | 1427 | * actions, which sounds a little better because it doesn't clash |
| | 1428 | * with the failure report that immediately follows. |
| | 1429 | */ |
| | 1430 | for (local i = 1, local len = vec.length() ; i <= len ; ++i) |
| | 1431 | { |
| | 1432 | local sub; |
| | 1433 | |
| | 1434 | /* get this item */ |
| | 1435 | local cur = vec[i]; |
| | 1436 | |
| | 1437 | /* |
| | 1438 | * If this is an implicit action announcement, and its |
| | 1439 | * corresponding action (or any nested action) failed, mark |
| | 1440 | * the implicit announcement as a mere attempt. Likewise, if |
| | 1441 | * we're interrupting the action for interactive input, it's |
| | 1442 | * likewise just an incomplete attempt. |
| | 1443 | */ |
| | 1444 | if (cur.ofKind(ImplicitActionAnnouncement) |
| | 1445 | && (sub = vec.valWhich( |
| | 1446 | {x: ((x.isFailure || x.isQuestion) |
| | 1447 | && (x.isPartOf(cur) |
| | 1448 | || x.isActionNestedIn(cur)))})) != nil) |
| | 1449 | { |
| | 1450 | /* |
| | 1451 | * it's either a failed attempt or an interruption for a |
| | 1452 | * question - note which one |
| | 1453 | */ |
| | 1454 | if (sub.isFailure) |
| | 1455 | cur.noteJustTrying(); |
| | 1456 | else |
| | 1457 | cur.noteQuestion(); |
| | 1458 | } |
| | 1459 | } |
| | 1460 | |
| | 1461 | /* |
| | 1462 | * Scan for implicit announcement groups. Since we're only |
| | 1463 | * scanning for runs of two or more announcements, we can stop |
| | 1464 | * scanning one short of the end of the list - there's no need to |
| | 1465 | * check the last item because it can't possibly be followed by |
| | 1466 | * another item. Thus, scan while i < len. |
| | 1467 | */ |
| | 1468 | for (local i = 1, local len = vec.length() ; i < len ; ++i) |
| | 1469 | { |
| | 1470 | local origI = i; |
| | 1471 | |
| | 1472 | /* get this item */ |
| | 1473 | local cur = vec[i]; |
| | 1474 | |
| | 1475 | /* |
| | 1476 | * If it's an implied action announcement, and the next one |
| | 1477 | * qualifies for group inclusion, build a group. Note that |
| | 1478 | * because we only loop until we reach the second-to-last |
| | 1479 | * item, we know for sure there is indeed a next item to |
| | 1480 | * index here. |
| | 1481 | */ |
| | 1482 | if (cur.ofKind(ImplicitActionAnnouncement) |
| | 1483 | && canGroupWith(cur, vec[i+1])) |
| | 1484 | { |
| | 1485 | local j; |
| | 1486 | local groupVec; |
| | 1487 | |
| | 1488 | /* create a vector to hold the re-sorted group listing */ |
| | 1489 | groupVec = new Vector(16); |
| | 1490 | |
| | 1491 | /* |
| | 1492 | * Scan items for grouping. This time, we want to scan |
| | 1493 | * to the last (not second-to-last) item in the main |
| | 1494 | * list, since we could conceivably group everything |
| | 1495 | * remaining. |
| | 1496 | */ |
| | 1497 | for (j = i ; j <= len ; ) |
| | 1498 | { |
| | 1499 | /* get this item */ |
| | 1500 | cur = vec[j]; |
| | 1501 | |
| | 1502 | /* unstack any recursive grouping */ |
| | 1503 | j = unstackRecursiveGroup(groupVec, vec, j); |
| | 1504 | |
| | 1505 | /* |
| | 1506 | * if we've used now everything in the list, or the |
| | 1507 | * next item can't be grouped with the current item, |
| | 1508 | * we're done |
| | 1509 | */ |
| | 1510 | if (j > len || !canGroupWith(cur, vec[j])) |
| | 1511 | break; |
| | 1512 | } |
| | 1513 | |
| | 1514 | /* process default object announcements */ |
| | 1515 | processDefaultAnnouncements(groupVec); |
| | 1516 | |
| | 1517 | /* build the composite message for the entire group */ |
| | 1518 | vec[i].messageText_ = implicitAnnouncementGrouper |
| | 1519 | .compositeMessage(groupVec); |
| | 1520 | |
| | 1521 | /* |
| | 1522 | * Clear the messages in the second through last grouped |
| | 1523 | * announcements. Leave the report objects themselves |
| | 1524 | * intact, so that our internal structural record of the |
| | 1525 | * transcript remains as it was, but make them silent in |
| | 1526 | * the displayed text, since these messages are now |
| | 1527 | * subsumed into the combined first message. |
| | 1528 | */ |
| | 1529 | for (++i ; i < j ; ++i) |
| | 1530 | vec[i].messageText_ = ''; |
| | 1531 | |
| | 1532 | /* |
| | 1533 | * continue the main loop from the next element after the |
| | 1534 | * last one we included in the group |
| | 1535 | */ |
| | 1536 | i = j - 1; |
| | 1537 | } |
| | 1538 | |
| | 1539 | /* |
| | 1540 | * If this is an implied action or default object |
| | 1541 | * announcement that interrupts a set of regular command |
| | 1542 | * reports, and it's for an action nested within the action |
| | 1543 | * generating the reports, add a paragraph spacer before the |
| | 1544 | * implicit announcement. |
| | 1545 | */ |
| | 1546 | if ((cur.ofKind(ImplicitActionAnnouncement) |
| | 1547 | || cur.ofKind(DefaultObjectAnnouncement)) |
| | 1548 | && cur.messageText_ != '') |
| | 1549 | { |
| | 1550 | local j; |
| | 1551 | |
| | 1552 | /* scan back for the nearest announcement with text */ |
| | 1553 | for (j = origI - 1 ; j >= 1 && vec[j].messageText_ == '' ; |
| | 1554 | --j) ; |
| | 1555 | |
| | 1556 | /* |
| | 1557 | * if it's a regular command report, and our implied or |
| | 1558 | * default announcement is nested within it, add a |
| | 1559 | * paragraph spacer |
| | 1560 | */ |
| | 1561 | if (j >= 1 |
| | 1562 | && vec[j].ofKind(FullCommandReport) |
| | 1563 | && cur.isActionNestedIn(vec[j])) |
| | 1564 | { |
| | 1565 | /* |
| | 1566 | * insert a paragraph spacer before the announcement |
| | 1567 | * - this will make the implied action and its |
| | 1568 | * results stand out as separate actions, rather than |
| | 1569 | * running everything together without spacing |
| | 1570 | */ |
| | 1571 | vec.insertAt(origI, new GroupSeparatorMessage(cur)); |
| | 1572 | |
| | 1573 | /* adjust our indices for the insertion */ |
| | 1574 | ++i; |
| | 1575 | ++len; |
| | 1576 | } |
| | 1577 | } |
| | 1578 | } |
| | 1579 | } |
| | 1580 | |
| | 1581 | /* |
| | 1582 | * "Unstack" a recursive group of nested announcements. Adds the |
| | 1583 | * recursive group to the output group vector in chronological order, |
| | 1584 | * and returns the index of the next item after the recursive group. |
| | 1585 | * |
| | 1586 | * A recursive group is a set of nested implicit commands, where one |
| | 1587 | * implicit command triggered another, which triggered another, and |
| | 1588 | * so on. The innermost of the nested set is the one that's actually |
| | 1589 | * executed first chronologically, since an implied command must be |
| | 1590 | * carried out before its enclosing command can proceed. For |
| | 1591 | * example: |
| | 1592 | * |
| | 1593 | *. >go south |
| | 1594 | *. (first opening the door) |
| | 1595 | *. (first unlocking the door) |
| | 1596 | *. (first taking the key out of the bag) |
| | 1597 | * |
| | 1598 | * Going south implies opening the door, but before we can open the |
| | 1599 | * door, we must unlock it, and before we can unlock it we must be |
| | 1600 | * holding the key. In report order, the innermost command is listed |
| | 1601 | * last, since it's nested within the enclosing commands. |
| | 1602 | * Chronologically, though, the innermost command is actually |
| | 1603 | * executed first. The purpose of this routine is to unstack these |
| | 1604 | * nested sets, rearranging them into chronological order. |
| | 1605 | */ |
| | 1606 | unstackRecursiveGroup(groupVec, vec, idx) |
| | 1607 | { |
| | 1608 | local cur; |
| | 1609 | |
| | 1610 | /* remember the item we're tasked to work on */ |
| | 1611 | cur = vec[idx]; |
| | 1612 | |
| | 1613 | /* skip the current item */ |
| | 1614 | ++idx; |
| | 1615 | |
| | 1616 | /* |
| | 1617 | * Scan for items nested within vec[idx]. Process each child |
| | 1618 | * item first. An item is nested within us if can be grouped |
| | 1619 | * with us, and its action is a child of our action. |
| | 1620 | */ |
| | 1621 | for (local len = vec.length() ; idx <= len ; ) |
| | 1622 | { |
| | 1623 | /* if the next item is nested within 'cur', process it */ |
| | 1624 | if (canGroupWith(cur, vec[idx]) |
| | 1625 | && vec[idx].getAction().isNestedIn(cur.getAction())) |
| | 1626 | { |
| | 1627 | /* |
| | 1628 | * It's nested with us - process it recursively. Since |
| | 1629 | * our goal is to unstack these reports into |
| | 1630 | * chronological order, we must process our children |
| | 1631 | * first, so that they get added to the group vector |
| | 1632 | * first, since children chronologically predede their |
| | 1633 | * parents. |
| | 1634 | */ |
| | 1635 | idx = unstackRecursiveGroup(groupVec, vec, idx); |
| | 1636 | } |
| | 1637 | else |
| | 1638 | { |
| | 1639 | /* it's not nested within us, so we're done */ |
| | 1640 | break; |
| | 1641 | } |
| | 1642 | } |
| | 1643 | |
| | 1644 | /* add our item to the result vector */ |
| | 1645 | groupVec.append(cur); |
| | 1646 | |
| | 1647 | /* |
| | 1648 | * return the index of the next item; this is simply the current |
| | 1649 | * 'idx' value, since we've advanced it past each item we've |
| | 1650 | * processed |
| | 1651 | */ |
| | 1652 | return idx; |
| | 1653 | } |
| | 1654 | |
| | 1655 | /* |
| | 1656 | * Process default object announcements in a grouped message vector. |
| | 1657 | * |
| | 1658 | * Default object announcements come in two flavors: with and without |
| | 1659 | * message text. Those without message text are present purely to |
| | 1660 | * retain a structural record of the default object in the internal |
| | 1661 | * transcript; we can simply remove these, since the actions that |
| | 1662 | * created them didn't even want default messages. For those that do |
| | 1663 | * include message text, remove them as well, but also use their |
| | 1664 | * actions to replace the corresponding parent actions, so that the |
| | 1665 | * parent actions reflect what actually happened with the final |
| | 1666 | * defaulted objects. |
| | 1667 | */ |
| | 1668 | processDefaultAnnouncements(vec) |
| | 1669 | { |
| | 1670 | /* scan the vector for default announcements */ |
| | 1671 | for (local i = 1, local len = vec.length() ; i <= len ; ++i) |
| | 1672 | { |
| | 1673 | local cur = vec[i]; |
| | 1674 | |
| | 1675 | /* if this is a default announcement, process it */ |
| | 1676 | if (cur.ofKind(DefaultObjectAnnouncement)) |
| | 1677 | { |
| | 1678 | /* |
| | 1679 | * If it has a message, use its action to replace the |
| | 1680 | * parent action. The only way an implied command can |
| | 1681 | * have a defaulted object is for the implied command to |
| | 1682 | * have been stated with too few objects, so that an |
| | 1683 | * askForIobj (for example) occurred. In such cases, the |
| | 1684 | * default announcement will be a child action of the |
| | 1685 | * original underspecified action, so we can simply find |
| | 1686 | * the original action and replace it with the defaulted |
| | 1687 | * action. |
| | 1688 | */ |
| | 1689 | if (cur.messageText_ != '') |
| | 1690 | { |
| | 1691 | /* |
| | 1692 | * Scan for the parent announcement. |
| | 1693 | * |
| | 1694 | * Note that the implicit announcement containing the |
| | 1695 | * parent action will follow the default announcement |
| | 1696 | * in the result list, since the default announcement |
| | 1697 | * is a child of the parent. |
| | 1698 | */ |
| | 1699 | for (local j = i + 1 ; j <= len ; ++j) |
| | 1700 | { |
| | 1701 | /* if this is the parent action, replace it */ |
| | 1702 | if (vec[j].getAction() |
| | 1703 | == cur.getAction().parentAction) |
| | 1704 | { |
| | 1705 | /* this is it - replace the action */ |
| | 1706 | vec[j].setAction(cur.getAction()); |
| | 1707 | |
| | 1708 | /* no need to look any further */ |
| | 1709 | break; |
| | 1710 | } |
| | 1711 | } |
| | 1712 | } |
| | 1713 | |
| | 1714 | /* remove the default announcement from the list */ |
| | 1715 | vec.removeElementAt(i); |
| | 1716 | |
| | 1717 | /* adjust our list index and length for the deletion */ |
| | 1718 | --i; |
| | 1719 | --len; |
| | 1720 | } |
| | 1721 | } |
| | 1722 | } |
| | 1723 | |
| | 1724 | /* |
| | 1725 | * Can we group the second item with the first? Returns true if the |
| | 1726 | * second item is also an implicit action announcement, or it's a |
| | 1727 | * default object announcement whose parent action is the first |
| | 1728 | * item's action. |
| | 1729 | */ |
| | 1730 | canGroupWith(a, b) |
| | 1731 | { |
| | 1732 | /* if 'b' is also an implicit announcement, we can include it */ |
| | 1733 | if (b.ofKind(ImplicitActionAnnouncement)) |
| | 1734 | return true; |
| | 1735 | |
| | 1736 | /* |
| | 1737 | * if 'b' is a default object announcement, and has the same |
| | 1738 | * parent action as 'a', then we can group it; otherwise we can't |
| | 1739 | */ |
| | 1740 | return (b.ofKind(DefaultObjectAnnouncement) |
| | 1741 | && b.getAction().parentAction == a.getAction()); |
| | 1742 | } |
| | 1743 | ; |
| | 1744 | |
| | 1745 | /* ------------------------------------------------------------------------ */ |
| | 1746 | /* |
| | 1747 | * Transcript Transform: Complex Multi-object Separation. If we have an |
| | 1748 | * action that's being applied to one of a bunch of iterated objects, and |
| | 1749 | * the action has any implied command announcements associated with it, |
| | 1750 | * we'll set off the result for this command from its preceding and |
| | 1751 | * following commands by a paragraph separator. |
| | 1752 | */ |
| | 1753 | complexMultiTransform: TranscriptTransform |
| | 1754 | applyTransform(trans, vec) |
| | 1755 | { |
| | 1756 | /* scan the list for multi-object announcements */ |
| | 1757 | foreach (local cur in vec) |
| | 1758 | { |
| | 1759 | /* if it's a multi-object announcement, check it */ |
| | 1760 | if (cur.ofKind(MultiObjectAnnouncement)) |
| | 1761 | { |
| | 1762 | local idx; |
| | 1763 | local cnt; |
| | 1764 | local sep; |
| | 1765 | |
| | 1766 | /* |
| | 1767 | * We have a multi-object announcement. If we find only |
| | 1768 | * one other report within the group, and the report's |
| | 1769 | * text is short, let this report run together with its |
| | 1770 | * neighbors without any additional visual separation. |
| | 1771 | * Otherwise, set this group apart from its neighbors by |
| | 1772 | * adding a paragraph break before and after the group; |
| | 1773 | * this will make the results easier to read by visually |
| | 1774 | * separating each longish response as a separate |
| | 1775 | * paragraph. |
| | 1776 | * |
| | 1777 | * First, find the current item's index. |
| | 1778 | */ |
| | 1779 | idx = vec.indexWhich({x: x == cur}); |
| | 1780 | |
| | 1781 | /* |
| | 1782 | * now scan subsequent items in the same command |
| | 1783 | * iteration, and check to see if (1) we have more than |
| | 1784 | * one item, or (2) the item has a longish message |
| | 1785 | */ |
| | 1786 | for (cnt = 0, ++idx, sep = nil ; |
| | 1787 | idx <= vec.length() && cnt < 2 ; ++idx, ++cnt) |
| | 1788 | { |
| | 1789 | local sub = vec[idx]; |
| | 1790 | |
| | 1791 | /* if we've reached the end of the group, stop scanning */ |
| | 1792 | if (sub.iter_ != cur.iter_) |
| | 1793 | break; |
| | 1794 | |
| | 1795 | /* |
| | 1796 | * If it has long text, add visual separation. Note |
| | 1797 | * that "long" is just a heuristic, because we can't |
| | 1798 | * tell whether the text will wrap in any given |
| | 1799 | * interpreter - that depends on the width of the |
| | 1800 | * interpreter window and the font size, among other |
| | 1801 | * things, and we have no way of knowing any of this |
| | 1802 | * here. |
| | 1803 | */ |
| | 1804 | if (sub.ofKind(CommandReportMessage) |
| | 1805 | && sub.messageText_ != nil |
| | 1806 | && sub.messageText_.length() > 60) |
| | 1807 | { |
| | 1808 | /* it's long - add separation */ |
| | 1809 | sep = true; |
| | 1810 | break; |
| | 1811 | } |
| | 1812 | } |
| | 1813 | |
| | 1814 | /* if we need separation, add it now */ |
| | 1815 | if (sep || cnt > 1) |
| | 1816 | { |
| | 1817 | /* |
| | 1818 | * This is indeed a complex iterated item. Set it |
| | 1819 | * off by paragraph breaks before and after the |
| | 1820 | * iteration. |
| | 1821 | * |
| | 1822 | * First, find the first item in this iteration. If |
| | 1823 | * it's not the first item in the whole transcript, |
| | 1824 | * insert a separator before it. |
| | 1825 | */ |
| | 1826 | idx = vec.indexWhich({x: x.iter_ == cur.iter_}); |
| | 1827 | if (idx != 1) |
| | 1828 | vec.insertAt(idx, new GroupSeparatorMessage(cur)); |
| | 1829 | |
| | 1830 | /* |
| | 1831 | * Next, find the last item in this iteration. If |
| | 1832 | * it's no the last item in the entire transcript, |
| | 1833 | * add a separator after it. |
| | 1834 | */ |
| | 1835 | idx = vec.lastIndexWhich({x: x.iter_ == cur.iter_}); |
| | 1836 | if (idx != vec.length()) |
| | 1837 | vec.insertAt(idx + 1, new GroupSeparatorMessage(cur)); |
| | 1838 | } |
| | 1839 | } |
| | 1840 | |
| | 1841 | /* |
| | 1842 | * if it's a command result from an implied command, and we |
| | 1843 | * have another command result following from the enclosing |
| | 1844 | * command, add a separator between this result and the next |
| | 1845 | * result |
| | 1846 | */ |
| | 1847 | if (cur.ofKind(CommandReportMessage) && cur.isActionImplicit) |
| | 1848 | { |
| | 1849 | local idx; |
| | 1850 | |
| | 1851 | /* get the index of this element */ |
| | 1852 | idx = vec.indexOf(cur); |
| | 1853 | |
| | 1854 | /* |
| | 1855 | * if there's another element following, check to see if |
| | 1856 | * it's a command report for an enclosing action (i.e., |
| | 1857 | * an action that initiated this implied action) |
| | 1858 | */ |
| | 1859 | if (idx < vec.length()) |
| | 1860 | { |
| | 1861 | local nxt; |
| | 1862 | |
| | 1863 | /* get the next element */ |
| | 1864 | nxt = vec[idx + 1]; |
| | 1865 | |
| | 1866 | /* |
| | 1867 | * if it's a command report for an action that |
| | 1868 | * encloses this action, or it's another implicit |
| | 1869 | * announcement, then put a separator before it |
| | 1870 | */ |
| | 1871 | if ((nxt.ofKind(CommandReportMessage) |
| | 1872 | && nxt.getAction() != cur.getAction() |
| | 1873 | && cur.isActionNestedIn(nxt)) |
| | 1874 | || nxt.ofKind(ImplicitActionAnnouncement)) |
| | 1875 | |
| | 1876 | { |
| | 1877 | /* add a separator */ |
| | 1878 | vec.insertAt(idx + 1, |
| | 1879 | new InternalSeparatorMessage(cur)); |
| | 1880 | } |
| | 1881 | } |
| | 1882 | } |
| | 1883 | } |
| | 1884 | } |
| | 1885 | ; |
| | 1886 | |
| | 1887 | /* ------------------------------------------------------------------------ */ |
| | 1888 | /* |
| | 1889 | * Invoke a callback function using a transcript of the given class. |
| | 1890 | * Returns the return value of the callback function. |
| | 1891 | */ |
| | 1892 | withCommandTranscript(transcriptClass, func) |
| | 1893 | { |
| | 1894 | local transcript; |
| | 1895 | local oldTranscript; |
| | 1896 | |
| | 1897 | /* |
| | 1898 | * if we already have an active transcript, just invoke the |
| | 1899 | * function, running everything through the existing active |
| | 1900 | * transcript |
| | 1901 | */ |
| | 1902 | if (gTranscript != nil && gTranscript.isActive) |
| | 1903 | { |
| | 1904 | /* invoke the callback and return the result */ |
| | 1905 | return (func)(); |
| | 1906 | } |
| | 1907 | |
| | 1908 | /* |
| | 1909 | * Create a transcript of the given class. Make the transcript |
| | 1910 | * transient, since it's effectively part of the output stream state |
| | 1911 | * and thus shouldn't be saved or undone. |
| | 1912 | */ |
| | 1913 | transcript = transcriptClass.createTransientInstance(); |
| | 1914 | |
| | 1915 | /* make this transcript the current global transcript */ |
| | 1916 | oldTranscript = gTranscript; |
| | 1917 | gTranscript = transcript; |
| | 1918 | |
| | 1919 | /* install the transcript as a filter on the main output stream */ |
| | 1920 | mainOutputStream.addOutputFilter(transcript); |
| | 1921 | |
| | 1922 | /* make sure we undo our global changes before we leave */ |
| | 1923 | try |
| | 1924 | { |
| | 1925 | /* invoke the callback and return the result */ |
| | 1926 | return (func)(); |
| | 1927 | } |
| | 1928 | finally |
| | 1929 | { |
| | 1930 | /* uninstall the transcript output filter */ |
| | 1931 | mainOutputStream.removeOutputFilter(transcript); |
| | 1932 | |
| | 1933 | /* restore the previous global transcript */ |
| | 1934 | gTranscript = oldTranscript; |
| | 1935 | |
| | 1936 | /* show the transcript results */ |
| | 1937 | transcript.showReports(true); |
| | 1938 | } |
| | 1939 | } |
| | 1940 | |