| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library: command execution |
| | 7 | * |
| | 8 | * This module defines functions that perform command execution. |
| | 9 | */ |
| | 10 | |
| | 11 | #include "adv3.h" |
| | 12 | |
| | 13 | |
| | 14 | /* ------------------------------------------------------------------------ */ |
| | 15 | /* |
| | 16 | * Execute a command line, as issued by the given actor and as given as a |
| | 17 | * list of tokens. |
| | 18 | * |
| | 19 | * If 'firstInSentence' is true, we're at the start of a "sentence." The |
| | 20 | * meaning and effect of this may vary by language. In English, a |
| | 21 | * sentence ends with certain punctuation marks (a period, semicolon, |
| | 22 | * exclamation mark, or question mark), so anything after one of these |
| | 23 | * punctuation marks is the start of a new sentence. Also in English, we |
| | 24 | * can address a command to an explicit target actor using the "actor," |
| | 25 | * prefix syntax, which we can't use except at the start of a sentence. |
| | 26 | * |
| | 27 | * If the command line consists of multiple commands, we will only |
| | 28 | * actually execute the first command before returning. We'll schedule |
| | 29 | * any additional commands for later execution by putting them into the |
| | 30 | * target actor's pending command queue before we return, but we won't |
| | 31 | * actually execute them. |
| | 32 | */ |
| | 33 | executeCommand(targetActor, issuingActor, toks, firstInSentence) |
| | 34 | { |
| | 35 | local actorPhrase; |
| | 36 | local actorSpecified; |
| | 37 | |
| | 38 | /* |
| | 39 | * Turn on sense caching while we're working, until execution |
| | 40 | * begins. The parsing and resolution phases of command processing |
| | 41 | * don't involve any changes to game state, so we can safely cache |
| | 42 | * sense information; caching sense information during these phases |
| | 43 | * is desirable because these steps (noun resolution in particular) |
| | 44 | * involve repeated inspection of the current sensory environment, |
| | 45 | * which can require expensive calculations. |
| | 46 | */ |
| | 47 | libGlobal.enableSenseCache(); |
| | 48 | |
| | 49 | /* we don't have an explicit actor phrase yet */ |
| | 50 | actorPhrase = nil; |
| | 51 | |
| | 52 | /* presume an actor will not be specified */ |
| | 53 | actorSpecified = nil; |
| | 54 | |
| | 55 | /* |
| | 56 | * If this is the start of a new sentence, and the issuing actor |
| | 57 | * wants to cancel any target actor designation at the end of each |
| | 58 | * sentence, change the target actor back to the issuing actor. |
| | 59 | */ |
| | 60 | if (firstInSentence |
| | 61 | && issuingActor != targetActor |
| | 62 | && issuingActor.revertTargetActorAtEndOfSentence) |
| | 63 | { |
| | 64 | /* switch to the target actor */ |
| | 65 | targetActor = issuingActor; |
| | 66 | |
| | 67 | /* switch to the issuer's sense context */ |
| | 68 | senseContext.setSenseContext(targetActor, sight); |
| | 69 | } |
| | 70 | |
| | 71 | /* |
| | 72 | * Keep going until we've processed the command. This might take |
| | 73 | * several iterations, because we might have replacement commands to |
| | 74 | * execute. |
| | 75 | */ |
| | 76 | parseTokenLoop: |
| | 77 | for (;;) |
| | 78 | { |
| | 79 | local lst; |
| | 80 | local action; |
| | 81 | local match; |
| | 82 | local nextIdx; |
| | 83 | local nextCommandTokens; |
| | 84 | local extraIdx; |
| | 85 | local extraTokens; |
| | 86 | |
| | 87 | /* |
| | 88 | * Catch any errors that occur while executing the command, |
| | 89 | * since some of them are signals to us that we should reparse |
| | 90 | * some new text read or generated deep down inside the command |
| | 91 | * processing. |
| | 92 | */ |
| | 93 | try |
| | 94 | { |
| | 95 | local rankings; |
| | 96 | |
| | 97 | /* we have no extra tokens yet */ |
| | 98 | extraTokens = []; |
| | 99 | |
| | 100 | /* |
| | 101 | * Parse the token list. If this is the first command on |
| | 102 | * the command line, allow an actor prefix. Otherwise, just |
| | 103 | * look for a command. |
| | 104 | */ |
| | 105 | lst = (firstInSentence ? firstCommandPhrase : commandPhrase) |
| | 106 | .parseTokens(toks, cmdDict); |
| | 107 | |
| | 108 | /* |
| | 109 | * As a first cut at reducing the list of possible matches |
| | 110 | * to those that make sense, eliminate from this list any |
| | 111 | * matches which do not have valid actions. In grammars for |
| | 112 | * "scrambling" languages (i.e., languages with flexible |
| | 113 | * word ordering), it is possible to construct commands that |
| | 114 | * fit the grammatical rules of sentence construction but |
| | 115 | * which make no sense because of the specific constraints |
| | 116 | * of the verbs involved; we can filter out such nonsense |
| | 117 | * interpretations immediately by keeping only those |
| | 118 | * structural interpretations that can resolve to valid |
| | 119 | * actions. |
| | 120 | */ |
| | 121 | lst = lst.subset( |
| | 122 | {x: x.resolveFirstAction(issuingActor, targetActor) != nil}); |
| | 123 | |
| | 124 | /* if we have no matches, the command isn't understood */ |
| | 125 | if (lst.length() == 0) |
| | 126 | { |
| | 127 | /* |
| | 128 | * If this is a first-in-sentence phrase, try looking for |
| | 129 | * a target actor phrase. If we can find one, we can |
| | 130 | * direct the 'oops' to that actor, to allow the game to |
| | 131 | * customize messages more specifically. |
| | 132 | */ |
| | 133 | if (firstInSentence) |
| | 134 | { |
| | 135 | local i; |
| | 136 | |
| | 137 | /* try parsing an "actor, <unknown>" phrase */ |
| | 138 | lst = actorBadCommandPhrase.parseTokens(toks, cmdDict); |
| | 139 | |
| | 140 | /* if we got any matches, try to resolve actors */ |
| | 141 | lst = lst.mapAll({x: x.resolveNouns( |
| | 142 | issuingActor, issuingActor, |
| | 143 | new ActorResolveResults())}); |
| | 144 | |
| | 145 | /* drop any that didn't yield any results */ |
| | 146 | lst = lst.subset({x: x != nil && x.length() != 0}); |
| | 147 | |
| | 148 | /* |
| | 149 | * |
| | 150 | * if anything's left, and one of the entries |
| | 151 | * resolves to an actor, arbitrarily pick the |
| | 152 | * first such entry use the resolved actor as the |
| | 153 | * new target actor |
| | 154 | */ |
| | 155 | if (lst.length() != 0 |
| | 156 | && (i = lst.indexWhich( |
| | 157 | {x: x[1].obj_.ofKind(Actor)})) != nil) |
| | 158 | targetActor = lst[i][1].obj_; |
| | 159 | } |
| | 160 | |
| | 161 | /* |
| | 162 | * We don't understand the command. Check for unknown |
| | 163 | * words - if we have any, give them a chance to use |
| | 164 | * OOPS to correct a typo. |
| | 165 | */ |
| | 166 | tryOops(toks, issuingActor, targetActor, |
| | 167 | 1, toks, rmcCommand); |
| | 168 | |
| | 169 | /* |
| | 170 | * try running it by the SpecialTopic history to see if |
| | 171 | * they're trying to use a special topic in the wrong |
| | 172 | * context - if so, explain that they can't use the |
| | 173 | * command right now, rather than claiming that the |
| | 174 | * command is completely invalid |
| | 175 | */ |
| | 176 | if (specialTopicHistory.checkHistory(toks)) |
| | 177 | { |
| | 178 | /* the special command is not currently available */ |
| | 179 | targetActor.notifyParseFailure( |
| | 180 | issuingActor, &specialTopicInactive, []); |
| | 181 | } |
| | 182 | else |
| | 183 | { |
| | 184 | /* tell the issuer we didn't understand the command */ |
| | 185 | targetActor.notifyParseFailure( |
| | 186 | issuingActor, &commandNotUnderstood, []); |
| | 187 | } |
| | 188 | |
| | 189 | /* |
| | 190 | * we're done with this command, and we want to abort |
| | 191 | * any subsequent commands on the command line |
| | 192 | */ |
| | 193 | return; |
| | 194 | } |
| | 195 | |
| | 196 | /* show the matches if we're in debug mode */ |
| | 197 | dbgShowGrammarList(lst); |
| | 198 | |
| | 199 | /* |
| | 200 | * Perform a tentative resolution on each alternative |
| | 201 | * structural interpretation of the command, and rank the |
| | 202 | * interpretations in order of "goodness" as determined by |
| | 203 | * our ranking criteria encoded in CommandRanking. |
| | 204 | * |
| | 205 | * Note that we perform the tentative resolution and ranking |
| | 206 | * even if we have only one interpretation, because the |
| | 207 | * tentative resolution is often useful for the final |
| | 208 | * resolution pass. |
| | 209 | */ |
| | 210 | rankings = CommandRanking |
| | 211 | .sortByRanking(lst, issuingActor, targetActor); |
| | 212 | |
| | 213 | /* |
| | 214 | * Take the interpretation that came up best in the rankings |
| | 215 | * (or, if we have multiple at the same ranking, arbitrarily |
| | 216 | * pick the one that happened to come up first in the list) |
| | 217 | * - they're ranked in descending order of goodness, so take |
| | 218 | * the first one. |
| | 219 | */ |
| | 220 | match = rankings[1].match; |
| | 221 | |
| | 222 | /* if we're in debug mode, show the winner */ |
| | 223 | dbgShowGrammarWithCaption('Winner', match); |
| | 224 | |
| | 225 | /* |
| | 226 | * Get the token list for the rest of the command after what |
| | 227 | * we've parsed so far. |
| | 228 | * |
| | 229 | * Note that we'll start over and parse these tokens anew, |
| | 230 | * even though we might have parsed them already into a |
| | 231 | * subcommand on the previous iteration. Even if we already |
| | 232 | * parsed these tokens, we want to parse them again, because |
| | 233 | * we did not have a suitable context for evaluating the |
| | 234 | * semantic strengths of the possible structural |
| | 235 | * interpretations this command on the previous iteration; |
| | 236 | * for example, it was not possible to resolve noun phrases |
| | 237 | * in this command because we could not guess what the scope |
| | 238 | * would be when we got to this point. So, we'll simply |
| | 239 | * discard the previous match tree, and start over, treating |
| | 240 | * the new tokens as a brand new command. |
| | 241 | */ |
| | 242 | nextIdx = match.getNextCommandIndex(); |
| | 243 | nextCommandTokens = toks.sublist(nextIdx); |
| | 244 | |
| | 245 | /* if the pending command list is empty, make it nil */ |
| | 246 | if (nextCommandTokens.length() == 0) |
| | 247 | nextCommandTokens = nil; |
| | 248 | |
| | 249 | /* |
| | 250 | * Get the part of the token list that the match doesn't use |
| | 251 | * at all. These are the tokens following the tokens we |
| | 252 | * matched. |
| | 253 | */ |
| | 254 | extraIdx = match.tokenList.length() + 1; |
| | 255 | extraTokens = toks.sublist(extraIdx); |
| | 256 | |
| | 257 | /* |
| | 258 | * We now have the best match for the command tree. |
| | 259 | * |
| | 260 | * If the command has an actor clause, resolve the actor, so |
| | 261 | * that we can direct the command to that actor. If the |
| | 262 | * command has no actor clause, the command is to the actor |
| | 263 | * who issued the command. |
| | 264 | * |
| | 265 | * Do NOT process the actor clause if we've already done so. |
| | 266 | * If we edit the token list and retry the command after |
| | 267 | * this point, there is no need to resolve the actor part |
| | 268 | * again. Doing so could be bad - if resolving the actor |
| | 269 | * required player interaction, such as asking for help with |
| | 270 | * an ambiguous noun phrase, we do not want to go through |
| | 271 | * the same interaction again just because we have to edit |
| | 272 | * and retry a later part of the command. |
| | 273 | */ |
| | 274 | if (match.hasTargetActor()) |
| | 275 | { |
| | 276 | local actorResults; |
| | 277 | |
| | 278 | /* |
| | 279 | * If we haven't yet explicitly specified a target |
| | 280 | * actor, and the default target actor is different from |
| | 281 | * the issuing actor, then this is really a new command |
| | 282 | * from the issuing actor. |
| | 283 | * |
| | 284 | * First, an actor change mid-command is allowed only if |
| | 285 | * the issuing actor waits for NPC commands to be |
| | 286 | * carried out; if not, then we can't change the actor |
| | 287 | * mid-command. |
| | 288 | * |
| | 289 | * Second, since the command comes from the issuing |
| | 290 | * actor, not from the default target actor, we want to |
| | 291 | * issue the orders on the issuing actor's turn, so put |
| | 292 | * the command into the queue for the issuer, and let |
| | 293 | * the issuer re-parse the command on the issuer's next |
| | 294 | * turn. |
| | 295 | */ |
| | 296 | if (!actorSpecified && issuingActor != targetActor) |
| | 297 | { |
| | 298 | /* |
| | 299 | * don't allow the command if the issuing actor |
| | 300 | * doesn't wait for orders to be completed |
| | 301 | */ |
| | 302 | if (!issuingActor.issueCommandsSynchronously) |
| | 303 | { |
| | 304 | /* turn off any sense capturing */ |
| | 305 | senseContext.setSenseContext(nil, sight); |
| | 306 | |
| | 307 | /* show the error */ |
| | 308 | issuingActor.getParserMessageObj() |
| | 309 | .cannotChangeActor(); |
| | 310 | |
| | 311 | /* done */ |
| | 312 | return; |
| | 313 | } |
| | 314 | |
| | 315 | /* put the command into the issuer's queue */ |
| | 316 | issuingActor.addFirstPendingCommand( |
| | 317 | firstInSentence, issuingActor, toks); |
| | 318 | |
| | 319 | /* done */ |
| | 320 | return; |
| | 321 | } |
| | 322 | |
| | 323 | /* create an actor-specialized results object */ |
| | 324 | actorResults = new ActorResolveResults(); |
| | 325 | |
| | 326 | /* set up the actors in the results object */ |
| | 327 | actorResults.setActors(targetActor, issuingActor); |
| | 328 | |
| | 329 | /* resolve the actor object */ |
| | 330 | match.resolveNouns(issuingActor, targetActor, actorResults); |
| | 331 | |
| | 332 | /* get the target actor from the command */ |
| | 333 | targetActor = match.getTargetActor(); |
| | 334 | |
| | 335 | /* pull out the phrase specifying the actor */ |
| | 336 | actorPhrase = match.getActorPhrase(); |
| | 337 | |
| | 338 | /* |
| | 339 | * Copy antecedents from the issuing actor to the target |
| | 340 | * actor. Since the issuer is giving us a new command |
| | 341 | * here, pronouns will be given from the issuer's |
| | 342 | * perspective. |
| | 343 | */ |
| | 344 | targetActor.copyPronounAntecedentsFrom(issuingActor); |
| | 345 | |
| | 346 | /* let the actor phrase know we're actually using it */ |
| | 347 | match.execActorPhrase(issuingActor); |
| | 348 | |
| | 349 | /* |
| | 350 | * Ask the target actor if it's interested in the |
| | 351 | * command at all. This only applies when the actor was |
| | 352 | * actually specified - if an actor wasn't specified, |
| | 353 | * the command is either directed to the issuer itself, |
| | 354 | * in which case the command will always be accepted; or |
| | 355 | * the command is a continuation of a command line |
| | 356 | * previously accepted. |
| | 357 | */ |
| | 358 | if (!targetActor.acceptCommand(issuingActor)) |
| | 359 | { |
| | 360 | /* |
| | 361 | * the command was immediately rejected - abandon |
| | 362 | * the command and any subsequent commands on the |
| | 363 | * same line |
| | 364 | */ |
| | 365 | return; |
| | 366 | } |
| | 367 | |
| | 368 | /* note that an actor was specified */ |
| | 369 | actorSpecified = true; |
| | 370 | |
| | 371 | /* |
| | 372 | * Pull out the rest of the command (other than the |
| | 373 | * target actor specification) and start over with a |
| | 374 | * fresh parse of the whole thing. We must do this |
| | 375 | * because our tentative resolution pass that we used to |
| | 376 | * pick the best structural interpretation of the |
| | 377 | * command couldn't go far enough - since we didn't know |
| | 378 | * the actor involved, we weren't able to resolve nouns |
| | 379 | * in the rest of the command. Now that we know the |
| | 380 | * actor, we can start over and resolve everything in |
| | 381 | * the rest of the command, and thus choose the right |
| | 382 | * structural match for the command. |
| | 383 | */ |
| | 384 | toks = match.getCommandTokens(); |
| | 385 | |
| | 386 | /* what follows obviously isn't first in the sentence */ |
| | 387 | firstInSentence = nil; |
| | 388 | |
| | 389 | /* go back to parse the rest */ |
| | 390 | continue parseTokenLoop; |
| | 391 | } |
| | 392 | |
| | 393 | /* pull out the first action from the command */ |
| | 394 | action = match.resolveFirstAction(issuingActor, targetActor); |
| | 395 | |
| | 396 | /* |
| | 397 | * If the winning interpretation had any unknown words, run |
| | 398 | * a resolution pass to resolve those interactively, if |
| | 399 | * possible. We want to do this before doing any other |
| | 400 | * interactive resolution because OOPS has the unique |
| | 401 | * property of forcing us to reparse the command; if we |
| | 402 | * allowed any other interactive resolution to happen before |
| | 403 | * processing an OOPS, we'd likely have to repeat the other |
| | 404 | * resolution on the reparse, which would confuse and |
| | 405 | * irritate users by asking the same question more than once |
| | 406 | * for what is apparently the same command. |
| | 407 | */ |
| | 408 | if (rankings[1].unknownWordCount != 0) |
| | 409 | { |
| | 410 | /* |
| | 411 | * resolve using the OOPS results gatherer - this runs |
| | 412 | * essentially the same preliminary resolution process |
| | 413 | * as the ranking results gatherer, but does perform |
| | 414 | * interactive resolution of unknown words via OOPS |
| | 415 | */ |
| | 416 | match.resolveNouns( |
| | 417 | issuingActor, targetActor, |
| | 418 | new OopsResults(issuingActor, targetActor)); |
| | 419 | } |
| | 420 | |
| | 421 | /* |
| | 422 | * If the command is directed to a different actor than the |
| | 423 | * issuer, change to the target actor's sense context. |
| | 424 | */ |
| | 425 | if (actorSpecified && targetActor != issuingActor) |
| | 426 | senseContext.setSenseContext(targetActor, sight); |
| | 427 | |
| | 428 | /* set up a transcript to receive the command results */ |
| | 429 | withCommandTranscript(CommandTranscript, new function() |
| | 430 | { |
| | 431 | /* |
| | 432 | * Execute the action. |
| | 433 | * |
| | 434 | * If a target actor was specified, and it's not the same |
| | 435 | * as the issuing actor, this counts as a turn for the |
| | 436 | * issuing actor. |
| | 437 | */ |
| | 438 | executeAction(targetActor, actorPhrase, issuingActor, |
| | 439 | actorSpecified && issuingActor != targetActor, |
| | 440 | action); |
| | 441 | }); |
| | 442 | |
| | 443 | /* |
| | 444 | * If we have anything remaining on the command line, insert |
| | 445 | * the remaining commands at the start of the target actor's |
| | 446 | * command queue, since we want the target actor to continue |
| | 447 | * with the rest of this command line as its next operation. |
| | 448 | * |
| | 449 | * Since the remainder of the line represents part of the |
| | 450 | * work the actor pulled out of the queue in order to call |
| | 451 | * us, put the remainder back in at the START of the actor's |
| | 452 | * queue - it was before anything else that might be in the |
| | 453 | * actor's queue before, so it should stay ahead of anything |
| | 454 | * else now. |
| | 455 | */ |
| | 456 | if (nextCommandTokens != nil) |
| | 457 | { |
| | 458 | /* |
| | 459 | * Prepend the remaining commands to the actor's queue. |
| | 460 | * The next command is the start of a new sentence if |
| | 461 | * the command we just executed ends the sentence. |
| | 462 | */ |
| | 463 | targetActor.addFirstPendingCommand( |
| | 464 | match.isEndOfSentence(), issuingActor, nextCommandTokens); |
| | 465 | } |
| | 466 | |
| | 467 | /* |
| | 468 | * If the command was directed from the issuer to a |
| | 469 | * different target actor, and the issuer wants to wait for |
| | 470 | * the full set of issued commands to complete before |
| | 471 | * getting another turn, tell the issuer to begin waiting. |
| | 472 | */ |
| | 473 | if (actorSpecified && issuingActor != targetActor) |
| | 474 | issuingActor.waitForIssuedCommand(targetActor); |
| | 475 | |
| | 476 | /* we're done */ |
| | 477 | return; |
| | 478 | } |
| | 479 | catch (ParseFailureException rfExc) |
| | 480 | { |
| | 481 | /* |
| | 482 | * Parsing failed in such a way that we cannot proceed. |
| | 483 | * Tell the target actor to notify the issuing actor. |
| | 484 | */ |
| | 485 | rfExc.notifyActor(targetActor, issuingActor); |
| | 486 | |
| | 487 | /* |
| | 488 | * the command cannot proceed, so abandon any remaining |
| | 489 | * tokens on the command line |
| | 490 | */ |
| | 491 | return; |
| | 492 | } |
| | 493 | catch (CancelCommandLineException cclExc) |
| | 494 | { |
| | 495 | /* |
| | 496 | * if there are any tokens remaining, the game might want to |
| | 497 | * show an explanation |
| | 498 | */ |
| | 499 | if (nextCommandTokens != nil) |
| | 500 | targetActor.getParserMessageObj().explainCancelCommandLine(); |
| | 501 | |
| | 502 | /* stop now, abandoning the rest of the command line */ |
| | 503 | return; |
| | 504 | } |
| | 505 | catch (TerminateCommandException tcExc) |
| | 506 | { |
| | 507 | /* |
| | 508 | * the command cannot proceed - we can't do any more |
| | 509 | * processing of this command, so simply return, abandoning |
| | 510 | * any additional tokens we have |
| | 511 | */ |
| | 512 | return; |
| | 513 | } |
| | 514 | catch (RetryCommandTokensException rctExc) |
| | 515 | { |
| | 516 | /* |
| | 517 | * We want to replace the current command's token list with |
| | 518 | * the new token list - get the new list, and then go back |
| | 519 | * and start over processing it. |
| | 520 | * |
| | 521 | * Note that we must retain any tokens beyond those that the |
| | 522 | * match tree used. The exception only edits the current |
| | 523 | * match tree's matched tokens, since it doesn't have access |
| | 524 | * to any of the original tokens beyond those, so we must |
| | 525 | * now add back in any tokens beyond the originals. |
| | 526 | */ |
| | 527 | toks = rctExc.newTokens_ + extraTokens; |
| | 528 | |
| | 529 | /* go back and process the command again with the new tokens */ |
| | 530 | continue parseTokenLoop; |
| | 531 | } |
| | 532 | catch (ReplacementCommandStringException rcsExc) |
| | 533 | { |
| | 534 | local str; |
| | 535 | |
| | 536 | /* retrieve the new command string from the exception */ |
| | 537 | str = rcsExc.newCommand_; |
| | 538 | |
| | 539 | /* |
| | 540 | * if the command string is nil, it means that the command |
| | 541 | * has been fully handled already, so we simply return |
| | 542 | * without any further work |
| | 543 | */ |
| | 544 | if (str == nil) |
| | 545 | return; |
| | 546 | |
| | 547 | /* |
| | 548 | * Replace the entire command string with the one from the |
| | 549 | * exception - this cancels any previous command that we |
| | 550 | * had. |
| | 551 | */ |
| | 552 | toks = cmdTokenizer.tokenize(str); |
| | 553 | |
| | 554 | /* |
| | 555 | * we have a brand new command line, so we're starting a |
| | 556 | * brand new sentence |
| | 557 | */ |
| | 558 | firstInSentence = true; |
| | 559 | |
| | 560 | /* set the issuing and target actor according to the exception */ |
| | 561 | issuingActor = rcsExc.issuingActor_; |
| | 562 | targetActor = rcsExc.targetActor_; |
| | 563 | |
| | 564 | /* |
| | 565 | * Put this work into the target actor's work queue, so that |
| | 566 | * the issuer will carry out the command at the next |
| | 567 | * opportunity. This is a brand new command line, so it |
| | 568 | * starts a new sentence. |
| | 569 | */ |
| | 570 | targetActor.addPendingCommand(true, issuingActor, toks); |
| | 571 | |
| | 572 | /* we're done processing this command */ |
| | 573 | return; |
| | 574 | } |
| | 575 | } |
| | 576 | } |
| | 577 | |
| | 578 | /* ------------------------------------------------------------------------ */ |
| | 579 | /* |
| | 580 | * GlobalRemapping makes it possible to transform one action into another |
| | 581 | * globally - as opposed to the remapTo mechanism, which lets an object |
| | 582 | * involved in the command perform a remapping. The key difference |
| | 583 | * between global remappings and remapTo is that the latter can't happen |
| | 584 | * until after the objects are resolved (for fairly obvious reasons: each |
| | 585 | * remapTo mapping is associated with an object, so you can't know which |
| | 586 | * mapping to apply until you know which object is involved). In |
| | 587 | * contrast, global remappings are performed *before* object resolution - |
| | 588 | * this is possible because the mappings don't depend on the objects |
| | 589 | * involved in the action. |
| | 590 | * |
| | 591 | * Whenever an action is about to be executed, the parser runs through |
| | 592 | * all of the defined global remappings, and gives each one a chance to |
| | 593 | * remap the command. If any remapping succeeds, we replace the original |
| | 594 | * command with the remapped version, then repeat the scan of the global |
| | 595 | * remapping list from the start - we do another complete scan of the |
| | 596 | * list in case there's another global mapping that applies to the |
| | 597 | * remapped version of the command. We repeat this process until we make |
| | 598 | * it through the whole list without finding a remapping. |
| | 599 | * |
| | 600 | * GlobalRemapping instances are added to the master list of mappings |
| | 601 | * automatically at pre-init time, and any time you construct one |
| | 602 | * dynamically with 'new'. |
| | 603 | */ |
| | 604 | class GlobalRemapping: PreinitObject |
| | 605 | /* |
| | 606 | * Check for and apply a remapping. This method must be implemented |
| | 607 | * in each GlobalRemapping instance to perform the actual remapping |
| | 608 | * work. |
| | 609 | * |
| | 610 | * This routine should first check to see if the command is relevant |
| | 611 | * to this remapping. In most cases, this means checking that the |
| | 612 | * command matches some template, such as having a particular action |
| | 613 | * (verb) and combination of potential objects. Note that the |
| | 614 | * objects aren't fully resolved during global remapping - the whole |
| | 615 | * point of global remapping is to catch certain phrasings before we |
| | 616 | * get to the noun resolution phase - but the *phrases* involved will |
| | 617 | * be known, so the range of potential matches is knowable. |
| | 618 | * |
| | 619 | * If the routine decides that the action isn't relevant to this |
| | 620 | * remapping, it should simply return nil. |
| | 621 | * |
| | 622 | * If the action decides to remap the action, it must create a new |
| | 623 | * Action object representing the replacement version of the command. |
| | 624 | * Then, return a list, [targetActor, action], giving the new target |
| | 625 | * actor and the new action. You don't have to change the target |
| | 626 | * actor, of course, but it's included in the result so that you can |
| | 627 | * change it if you want to. For example, you could use this to |
| | 628 | * remap a command of the form "X, GIVE ME Y" to "ME, ASK X FOR Y" - |
| | 629 | * note that the target actor changes from X to ME. |
| | 630 | */ |
| | 631 | getRemapping(issuingActor, targetActor, action) |
| | 632 | { |
| | 633 | /* |
| | 634 | * this must be overridden to perform the actual remapping; by |
| | 635 | * default, simply return nil to indicate that we don't want to |
| | 636 | * remap this action |
| | 637 | */ |
| | 638 | return nil; |
| | 639 | } |
| | 640 | |
| | 641 | /* |
| | 642 | * Remapping order - the parser applies global remappings in |
| | 643 | * ascending order of this value. In most cases, the order shouldn't |
| | 644 | * matter, since most remappings should be narrow enough that a given |
| | 645 | * command will only be subject to one remapping rule. However, in |
| | 646 | * some cases you might need to define rules that overlap, so the |
| | 647 | * ordering lets you specify which one goes first. In most cases |
| | 648 | * you'll want to apply the more specific rule first. |
| | 649 | */ |
| | 650 | remappingOrder = 100 |
| | 651 | |
| | 652 | /* |
| | 653 | * Static class method: look for a remapping. This runs through the |
| | 654 | * master list of mappings, looking for a mapping that applies to the |
| | 655 | * given command. If we find one, we'll replace the command with the |
| | 656 | * remapped version, then start over with a fresh scan of the entire |
| | 657 | * list to see if there's a remapping for the *new* version of the |
| | 658 | * command. We repeat this until we get through the whole list |
| | 659 | * without finding a remapping. |
| | 660 | * |
| | 661 | * The return value is a list, [targetActor, action], giving the |
| | 662 | * resulting target actor and new action object. If we don't find |
| | 663 | * any remapping, this will simply be the original values passed in |
| | 664 | * as our arguments; if we do find a remapping, this will be the new |
| | 665 | * version of the command. |
| | 666 | */ |
| | 667 | findGlobalRemapping(issuingActor, targetActor, action) |
| | 668 | { |
| | 669 | /* get the global remapping list */ |
| | 670 | local v = GlobalRemapping.allGlobalRemappings; |
| | 671 | local cnt = v.length(); |
| | 672 | |
| | 673 | /* if necessary, sort the list */ |
| | 674 | if (GlobalRemapping.listNeedsSorting) |
| | 675 | { |
| | 676 | /* sort it by ascending remappingOrder value */ |
| | 677 | v.sort(SortAsc, {a, b: a.remappingOrder - b.remappingOrder}); |
| | 678 | |
| | 679 | /* note that it's now sorted */ |
| | 680 | GlobalRemapping.listNeedsSorting = nil; |
| | 681 | } |
| | 682 | |
| | 683 | /* |
| | 684 | * iterate through the list repeatedly, until we make it all the |
| | 685 | * way through without finding a mapping |
| | 686 | */ |
| | 687 | for (local done = nil ; !done ; ) |
| | 688 | { |
| | 689 | /* presume we won't find a remapping on this iteration */ |
| | 690 | done = true; |
| | 691 | |
| | 692 | /* run through the list, looking for a remapping */ |
| | 693 | for (local i = 1 ; i <= cnt ; ++i) |
| | 694 | { |
| | 695 | local rm; |
| | 696 | |
| | 697 | /* check for a remapping */ |
| | 698 | rm = v[i].getRemapping(issuingActor, targetActor, action); |
| | 699 | if (rm != nil) |
| | 700 | { |
| | 701 | /* found a remapping - apply it */ |
| | 702 | targetActor = rm[1]; |
| | 703 | action = rm[2]; |
| | 704 | |
| | 705 | /* |
| | 706 | * we found a remapping, so we have to repeat the |
| | 707 | * scan of the whole list - note that we're not done, |
| | 708 | * and break out of the current scan so that we start |
| | 709 | * over with a fresh scan |
| | 710 | */ |
| | 711 | done = nil; |
| | 712 | break; |
| | 713 | } |
| | 714 | } |
| | 715 | } |
| | 716 | |
| | 717 | /* return the final version of the command */ |
| | 718 | return [targetActor, action]; |
| | 719 | } |
| | 720 | |
| | 721 | /* pre-initialization: add each instance to the master list */ |
| | 722 | execute() |
| | 723 | { |
| | 724 | /* add me to the master list */ |
| | 725 | registerGlobalRemapping(); |
| | 726 | } |
| | 727 | |
| | 728 | /* construction: add myself to the master list */ |
| | 729 | construct() |
| | 730 | { |
| | 731 | /* add me to the master list */ |
| | 732 | registerGlobalRemapping(); |
| | 733 | } |
| | 734 | |
| | 735 | /* register myself with the global list, making this an active mapping */ |
| | 736 | registerGlobalRemapping() |
| | 737 | { |
| | 738 | /* add myself to the global list */ |
| | 739 | GlobalRemapping.allGlobalRemappings.append(self); |
| | 740 | |
| | 741 | /* note that a sort is required the next time we run */ |
| | 742 | GlobalRemapping.listNeedsSorting = true; |
| | 743 | } |
| | 744 | |
| | 745 | /* |
| | 746 | * unregister - this removes me from the global list, making this |
| | 747 | * mapping inactive: after being unregistered, the parser won't apply |
| | 748 | * this mapping to new commands |
| | 749 | */ |
| | 750 | unregisterGlobalRemapping() |
| | 751 | { |
| | 752 | GlobalRemapping.allGlobalRemappings.removeElement(self); |
| | 753 | } |
| | 754 | |
| | 755 | /* |
| | 756 | * Static class property: the master list of remappings. We build |
| | 757 | * this automatically at preinit time, and manipulate it via our |
| | 758 | * constructor. |
| | 759 | */ |
| | 760 | allGlobalRemappings = static new Vector(10) |
| | 761 | |
| | 762 | /* |
| | 763 | * static class property: the master list needs to be sorted; this is |
| | 764 | * set to true each time we update the list, so that the list scanner |
| | 765 | * knows to sort it before doing its scan |
| | 766 | */ |
| | 767 | listNeedsSorting = nil |
| | 768 | ; |
| | 769 | |
| | 770 | |
| | 771 | |
| | 772 | /* ------------------------------------------------------------------------ */ |
| | 773 | /* |
| | 774 | * Execute an action, as specified by an Action object. We'll resolve |
| | 775 | * the nouns in the action, then perform the action. |
| | 776 | */ |
| | 777 | executeAction(targetActor, targetActorPhrase, |
| | 778 | issuingActor, countsAsIssuerTurn, action) |
| | 779 | { |
| | 780 | local rm, results; |
| | 781 | |
| | 782 | startOver: |
| | 783 | /* check for a global remapping */ |
| | 784 | rm = GlobalRemapping.findGlobalRemapping( |
| | 785 | issuingActor, targetActor, action); |
| | 786 | targetActor = rm[1]; |
| | 787 | action = rm[2]; |
| | 788 | |
| | 789 | /* create a basic results object to handle the resolution */ |
| | 790 | results = new BasicResolveResults(); |
| | 791 | |
| | 792 | /* set up the actors in the results object */ |
| | 793 | results.setActors(targetActor, issuingActor); |
| | 794 | |
| | 795 | /* catch any "remap" signals while resolving noun phrases */ |
| | 796 | try |
| | 797 | { |
| | 798 | /* resolve noun phrases */ |
| | 799 | action.resolveNouns(issuingActor, targetActor, results); |
| | 800 | } |
| | 801 | catch (RemapActionSignal sig) |
| | 802 | { |
| | 803 | /* mark the new action as remapped */ |
| | 804 | sig.action_.setRemapped(action); |
| | 805 | |
| | 806 | /* get the new action from the signal */ |
| | 807 | action = sig.action_; |
| | 808 | |
| | 809 | /* start over with the new action */ |
| | 810 | goto startOver; |
| | 811 | } |
| | 812 | |
| | 813 | /* |
| | 814 | * Check to see if we should create an undo savepoint for the |
| | 815 | * command. If the action is not marked for inclusion in the undo |
| | 816 | * log, there is no need to log a savepoint for it. |
| | 817 | * |
| | 818 | * Don't save undo for nested commands. A nested command is part of |
| | 819 | * a main command, and we only want to save undo for the main |
| | 820 | * command, not for its individual sub-commands. |
| | 821 | */ |
| | 822 | if (action.includeInUndo |
| | 823 | && action.parentAction == nil |
| | 824 | && (targetActor.isPlayerChar() |
| | 825 | || (issuingActor.isPlayerChar() && countsAsIssuerTurn))) |
| | 826 | { |
| | 827 | /* |
| | 828 | * Remember the command we're about to perform, so that if we |
| | 829 | * undo to here we'll be able to report what we undid. Note that |
| | 830 | * we do this *before* setting the savepoint, because we want |
| | 831 | * after the undo to know the command we were about to issue at |
| | 832 | * the savepoint. |
| | 833 | */ |
| | 834 | libGlobal.lastCommandForUndo = action.getOrigText(); |
| | 835 | libGlobal.lastActorForUndo = |
| | 836 | (targetActorPhrase == nil |
| | 837 | ? nil |
| | 838 | : targetActorPhrase.getOrigText()); |
| | 839 | |
| | 840 | /* |
| | 841 | * set a savepoint here, so that we on 'undo' we'll restore |
| | 842 | * conditions to what they were just before we executed this |
| | 843 | * command |
| | 844 | */ |
| | 845 | savepoint(); |
| | 846 | } |
| | 847 | |
| | 848 | /* |
| | 849 | * If this counts as a turn for the issuer, adjust the issuer's busy |
| | 850 | * time. |
| | 851 | * |
| | 852 | * However, this doesn't apply if the command is conversational (that |
| | 853 | * is, it's something like "BOB, HELLO"). A conversational command |
| | 854 | * is conceptually carried out by the issuer, not the target actor, |
| | 855 | * since the action consists of the issuer actually saying something |
| | 856 | * to the target actor. The normal turn accounting in Action will |
| | 857 | * count a conversational command this way, so we don't have to do |
| | 858 | * the extra bookkeeping for such a command here. |
| | 859 | */ |
| | 860 | if (countsAsIssuerTurn && !action.isConversational(issuingActor)) |
| | 861 | { |
| | 862 | /* |
| | 863 | * note in the issuer that the target is the most recent |
| | 864 | * conversational partner |
| | 865 | */ |
| | 866 | issuingActor.lastInterlocutor = targetActor; |
| | 867 | |
| | 868 | /* make the issuer busy for the order-giving interval */ |
| | 869 | issuingActor.addBusyTime(nil, |
| | 870 | issuingActor.orderingTime(targetActor)); |
| | 871 | |
| | 872 | /* notify the target that this will be a non-idle turn */ |
| | 873 | targetActor.nonIdleTurn(); |
| | 874 | } |
| | 875 | |
| | 876 | /* |
| | 877 | * If the issuer is directing the command to a different actor, and |
| | 878 | * it's not a conversational command, check with the target actor to |
| | 879 | * see if it wants to accept the command. Don't check |
| | 880 | * conversational commands, since these aren't of the nature of |
| | 881 | * orders to be obeyed. |
| | 882 | */ |
| | 883 | if (issuingActor != targetActor |
| | 884 | && !action.isConversational(issuingActor) |
| | 885 | && !targetActor.obeyCommand(issuingActor, action)) |
| | 886 | { |
| | 887 | /* |
| | 888 | * if the issuing actor's "ordering time" is zero, make this take |
| | 889 | * up a turn anyway, just for the refusal |
| | 890 | */ |
| | 891 | if (issuingActor.orderingTime(targetActor) == 0) |
| | 892 | issuingActor.addBusyTime(nil, 1); |
| | 893 | |
| | 894 | /* |
| | 895 | * Since we're aborting the command, we won't get into the normal |
| | 896 | * execution for it. However, we might still want to save it for |
| | 897 | * an attempted re-issue with AGAIN, so do so explicitly now. |
| | 898 | */ |
| | 899 | action.saveActionForAgain(issuingActor, countsAsIssuerTurn, |
| | 900 | targetActor, targetActorPhrase); |
| | 901 | |
| | 902 | /* |
| | 903 | * This command was rejected, so don't process it any further, |
| | 904 | * and give up on processing any remaining commands on the same |
| | 905 | * command line. |
| | 906 | */ |
| | 907 | throw new TerminateCommandException(); |
| | 908 | } |
| | 909 | |
| | 910 | /* execute the action */ |
| | 911 | action.doAction(issuingActor, targetActor, targetActorPhrase, |
| | 912 | countsAsIssuerTurn); |
| | 913 | } |
| | 914 | |
| | 915 | /* ------------------------------------------------------------------------ */ |
| | 916 | /* |
| | 917 | * Try an implicit action. |
| | 918 | * |
| | 919 | * Returns true if the action was attempted, whether or not it |
| | 920 | * succeeded, nil if the command was not even attempted. We will not |
| | 921 | * attempt an implied command that verifies as "dangerous," since this |
| | 922 | * means that it should be obvious to the player character that such a |
| | 923 | * command should not be performed lightly. |
| | 924 | */ |
| | 925 | _tryImplicitAction(issuingActor, targetActor, msgProp, actionClass, [objs]) |
| | 926 | { |
| | 927 | local action; |
| | 928 | |
| | 929 | /* create an instance of the desired action class */ |
| | 930 | action = actionClass.createActionInstance(); |
| | 931 | |
| | 932 | /* mark the action as implicit */ |
| | 933 | action.setImplicit(msgProp); |
| | 934 | |
| | 935 | /* install the resolved objects in the action */ |
| | 936 | action.setResolvedObjects(objs...); |
| | 937 | |
| | 938 | /* |
| | 939 | * For an implicit action, we must check the objects involved to make |
| | 940 | * sure they're in scope. If any of the objects aren't in scope, |
| | 941 | * there is no way the actor would know to perform the command, so |
| | 942 | * the command would not be implied in the first place. Simply fail |
| | 943 | * without trying the command. |
| | 944 | */ |
| | 945 | if (!action.resolvedObjectsInScope()) |
| | 946 | return nil; |
| | 947 | |
| | 948 | /* |
| | 949 | * catch the abort-implicit signal, so we can turn it into a result |
| | 950 | * code for our caller instead of an exception |
| | 951 | */ |
| | 952 | try |
| | 953 | { |
| | 954 | /* in NPC mode, add a command separator before each implied action */ |
| | 955 | if (targetActor.impliedCommandMode() == ModeNPC) |
| | 956 | gTranscript.addCommandSep(); |
| | 957 | |
| | 958 | /* execute the action */ |
| | 959 | action.doAction(issuingActor, targetActor, nil, nil); |
| | 960 | |
| | 961 | /* |
| | 962 | * if the actor is in "NPC" mode for implied commands, do some |
| | 963 | * extra work |
| | 964 | */ |
| | 965 | if (targetActor.impliedCommandMode() == ModeNPC) |
| | 966 | { |
| | 967 | /* |
| | 968 | * we're in NPC mode, so if the implied action failed, then |
| | 969 | * act as though the command had never been attempted |
| | 970 | */ |
| | 971 | if (gTranscript.actionFailed(action)) |
| | 972 | { |
| | 973 | /* the implied command failed - act like we didn't even try */ |
| | 974 | return nil; |
| | 975 | } |
| | 976 | |
| | 977 | /* |
| | 978 | * In "NPC" mode, we display the results from implied |
| | 979 | * commands as though they had been explicitly entered as |
| | 980 | * separate actions. So, add visual separation after the |
| | 981 | * results from the implied command. |
| | 982 | */ |
| | 983 | gTranscript.addCommandSep(); |
| | 984 | } |
| | 985 | |
| | 986 | /* tell the caller we at least tried to execute the command */ |
| | 987 | return true; |
| | 988 | } |
| | 989 | catch (AbortImplicitSignal sig) |
| | 990 | { |
| | 991 | /* tell the caller we didn't execute the command at all */ |
| | 992 | return nil; |
| | 993 | } |
| | 994 | catch (ParseFailureException exc) |
| | 995 | { |
| | 996 | /* |
| | 997 | * Parse failure. If the actor is in NPC mode, we can't have |
| | 998 | * asked for a new command, so this must be some failure in |
| | 999 | * processing the implied command itself; most likely, we tried |
| | 1000 | * to resolve a missing object or the like and found that we |
| | 1001 | * couldn't perform interactive resolution (because of the NPC |
| | 1002 | * mode). In this case, simply treat this as a failure of the |
| | 1003 | * implied command itself, and act as though we didn't even try |
| | 1004 | * the implied command. |
| | 1005 | * |
| | 1006 | * If the actor is in player mode, then we *can* perform |
| | 1007 | * interactive resolution, so we won't have thrown a parser |
| | 1008 | * failure before trying to solve the problem interactively. The |
| | 1009 | * failure must therefore be in an interactive response. In this |
| | 1010 | * case, simply re-throw the failure so that it reaches the main |
| | 1011 | * parser. |
| | 1012 | */ |
| | 1013 | if (targetActor.impliedCommandMode() == ModeNPC) |
| | 1014 | { |
| | 1015 | /* NPC mode - the implied command itself failed */ |
| | 1016 | return nil; |
| | 1017 | } |
| | 1018 | else |
| | 1019 | { |
| | 1020 | /* player mode - interactive resolution failed */ |
| | 1021 | throw exc; |
| | 1022 | } |
| | 1023 | } |
| | 1024 | } |
| | 1025 | |
| | 1026 | /* ------------------------------------------------------------------------ */ |
| | 1027 | /* |
| | 1028 | * Run a replacement action. |
| | 1029 | */ |
| | 1030 | _replaceAction(actor, actionClass, [objs]) |
| | 1031 | { |
| | 1032 | /* run the replacement action as a nested action */ |
| | 1033 | _nestedAction(true, actor, actionClass, objs...); |
| | 1034 | |
| | 1035 | /* the invoking command is done */ |
| | 1036 | exit; |
| | 1037 | } |
| | 1038 | |
| | 1039 | /* ------------------------------------------------------------------------ */ |
| | 1040 | /* |
| | 1041 | * Resolve and execute a replacement action. This differs from the |
| | 1042 | * normal replacement action execution in that the action we execute |
| | 1043 | * requires resolution before execution. |
| | 1044 | */ |
| | 1045 | resolveAndReplaceAction(newAction) |
| | 1046 | { |
| | 1047 | /* prepare the replacement action */ |
| | 1048 | prepareNestedAction(true, nil, newAction); |
| | 1049 | |
| | 1050 | /* |
| | 1051 | * resolve and execute the new action, using the same target and |
| | 1052 | * issuing actors as the original action |
| | 1053 | */ |
| | 1054 | executeAction(gActor, nil, gIssuingActor, nil, newAction); |
| | 1055 | |
| | 1056 | /* the invoking command has been replaced, so it's done */ |
| | 1057 | exit; |
| | 1058 | } |
| | 1059 | |
| | 1060 | /* ------------------------------------------------------------------------ */ |
| | 1061 | /* |
| | 1062 | * Run an action as a new turn. Returns the CommandTranscript describing |
| | 1063 | * the action's results. |
| | 1064 | */ |
| | 1065 | _newAction(transcriptClass, issuingActor, targetActor, actionClass, [objs]) |
| | 1066 | { |
| | 1067 | local action; |
| | 1068 | |
| | 1069 | /* create an instance of the desired action class */ |
| | 1070 | action = actionClass.createActionInstance(); |
| | 1071 | |
| | 1072 | /* execute the command with the action instance */ |
| | 1073 | return newActionObj(transcriptClass, issuingActor, targetActor, |
| | 1074 | action, objs...); |
| | 1075 | } |
| | 1076 | |
| | 1077 | /* |
| | 1078 | * Run an action as a new turn. This is almost the same as _newAction, |
| | 1079 | * but should be used when the caller has already explicitly created an |
| | 1080 | * instance of the Action to be performed. |
| | 1081 | * |
| | 1082 | * If issuingActor is nil, we'll use the current global issuing actor; if |
| | 1083 | * that's also nil, we'll use the target actor. |
| | 1084 | * |
| | 1085 | * Returns a CommandTranscript object describing the result of the |
| | 1086 | * action. |
| | 1087 | */ |
| | 1088 | newActionObj(transcriptClass, issuingActor, targetActor, actionObj, [objs]) |
| | 1089 | { |
| | 1090 | /* create the results object and install it as the global transcript */ |
| | 1091 | return withCommandTranscript(transcriptClass, new function() |
| | 1092 | { |
| | 1093 | /* install the resolved objects in the action */ |
| | 1094 | actionObj.setResolvedObjects(objs...); |
| | 1095 | |
| | 1096 | /* |
| | 1097 | * if the issuing actor isn't specified, use the current global |
| | 1098 | * issuing actor; if that's also not set, use the target actor |
| | 1099 | */ |
| | 1100 | if (issuingActor == nil) |
| | 1101 | issuingActor = gIssuingActor; |
| | 1102 | if (issuingActor == nil) |
| | 1103 | issuingActor = targetActor; |
| | 1104 | |
| | 1105 | /* |
| | 1106 | * Execute the given action. Because this is a new action, |
| | 1107 | * execute the action in a new sense context for the given actor. |
| | 1108 | */ |
| | 1109 | callWithSenseContext(targetActor.isPlayerChar() |
| | 1110 | ? nil : targetActor, sight, |
| | 1111 | {: actionObj.doAction(issuingActor, targetActor, nil, nil)}); |
| | 1112 | |
| | 1113 | /* return the current global transcript object */ |
| | 1114 | return gTranscript; |
| | 1115 | }); |
| | 1116 | } |
| | 1117 | |
| | 1118 | /* ------------------------------------------------------------------------ */ |
| | 1119 | /* |
| | 1120 | * Run a nested action. 'isReplacement' has the same meaning as in |
| | 1121 | * execNestedAction(). |
| | 1122 | */ |
| | 1123 | _nestedAction(isReplacement, actor, actionClass, [objs]) |
| | 1124 | { |
| | 1125 | local action; |
| | 1126 | |
| | 1127 | /* create an instance of the desired action class */ |
| | 1128 | action = actionClass.createActionInstance(); |
| | 1129 | |
| | 1130 | /* install the resolved objects in the action */ |
| | 1131 | action.setResolvedObjects(objs...); |
| | 1132 | |
| | 1133 | /* execute the new action */ |
| | 1134 | execNestedAction(isReplacement, nil, actor, action); |
| | 1135 | } |
| | 1136 | |
| | 1137 | /* |
| | 1138 | * Execute a fully-constructed nested action. |
| | 1139 | * |
| | 1140 | * 'isReplacement' indicates whether the action is a full replacement or |
| | 1141 | * an ordinary nested action. If it's a replacement, then we use the |
| | 1142 | * game time taken by the replacement, and set the enclosing action |
| | 1143 | * (i.e., the current gAction) to take zero time. If it's an ordinary |
| | 1144 | * nested action, then we consider the nested action to take zero time, |
| | 1145 | * using the current action's time as the overall command time. |
| | 1146 | * |
| | 1147 | * 'isRemapping' indicates whether or not this is a remapped action. If |
| | 1148 | * we're remapping from one action to another, this will be true; for |
| | 1149 | * any other kind of nested or replacement action, this should be nil. |
| | 1150 | */ |
| | 1151 | execNestedAction(isReplacement, isRemapping, actor, action) |
| | 1152 | { |
| | 1153 | /* prepare the nested action */ |
| | 1154 | prepareNestedAction(isReplacement, isRemapping, action); |
| | 1155 | |
| | 1156 | /* execute the new action in the actor's sense context */ |
| | 1157 | callWithSenseContext( |
| | 1158 | actor.isPlayerChar() ? nil : actor, sight, |
| | 1159 | {: action.doAction(gIssuingActor, actor, nil, nil) }); |
| | 1160 | } |
| | 1161 | |
| | 1162 | /* |
| | 1163 | * Prepare a nested or replacement action for execution. |
| | 1164 | */ |
| | 1165 | prepareNestedAction(isReplacement, isRemapping, action) |
| | 1166 | { |
| | 1167 | /* |
| | 1168 | * if the original action is an implicit command, make the new |
| | 1169 | * command implicit as well |
| | 1170 | */ |
| | 1171 | if (gAction.isImplicit) |
| | 1172 | { |
| | 1173 | /* |
| | 1174 | * make the new action implicit, but don't describe it as a |
| | 1175 | * separate implicit command - it's effectively part of the |
| | 1176 | * original implicit command |
| | 1177 | */ |
| | 1178 | action.setImplicit(nil); |
| | 1179 | } |
| | 1180 | |
| | 1181 | /* mark the new action as nested */ |
| | 1182 | action.setNested(); |
| | 1183 | |
| | 1184 | /* a nested action is part of the enclosing action */ |
| | 1185 | action.setOriginalAction(gAction); |
| | 1186 | |
| | 1187 | /* if this is a remapping, mark it as such */ |
| | 1188 | if (isRemapping) |
| | 1189 | action.setRemapped(gAction); |
| | 1190 | |
| | 1191 | /* |
| | 1192 | * Set either the nested action's time or the enclosing (current) |
| | 1193 | * action's time to zero - we want to count only the time of one |
| | 1194 | * command or the other. |
| | 1195 | * |
| | 1196 | * If we're running an ordinary nested command, set the nested |
| | 1197 | * command's time to zero, since we want to consider it just a part |
| | 1198 | * of the enclosing command and thus to take no time of its own. |
| | 1199 | * |
| | 1200 | * If we're running a full replacement command, and we're replacing |
| | 1201 | * something other than an implied command, don't consider the |
| | 1202 | * enclosing command to take any time, since the enclosing command is |
| | 1203 | * carrying out its entire function via the replacement and thus |
| | 1204 | * requires no time of its own. If we're replacing an implied |
| | 1205 | * command, this doesn't apply, since the implied command defers to |
| | 1206 | * its enclosing command for timing. If we're replacing a command |
| | 1207 | * that already has zero action time, this also doesn't apply, since |
| | 1208 | * we're presumably replacing a command that's itself nested. |
| | 1209 | */ |
| | 1210 | if (isReplacement && !gAction.isImplicit && gAction.actionTime != 0) |
| | 1211 | gAction.zeroActionTime(); |
| | 1212 | else |
| | 1213 | action.actionTime = 0; |
| | 1214 | } |
| | 1215 | |
| | 1216 | /* ------------------------------------------------------------------------ */ |
| | 1217 | /* |
| | 1218 | * Run a previously-executed command as a nested action, re-resolving |
| | 1219 | * all of its objects to ensure they are still valid. |
| | 1220 | */ |
| | 1221 | nestedActionAgain(action) |
| | 1222 | { |
| | 1223 | /* reset the any cached information for the new command context */ |
| | 1224 | action.resetAction(); |
| | 1225 | |
| | 1226 | /* mark the action as nested */ |
| | 1227 | action.setNested(); |
| | 1228 | action.setOriginalAction(gAction); |
| | 1229 | |
| | 1230 | /* |
| | 1231 | * do not count any time for the nested action, since it's merely |
| | 1232 | * part of the main turn and doesn't count as a separate turn of its |
| | 1233 | * own |
| | 1234 | */ |
| | 1235 | action.actionTime = 0; |
| | 1236 | |
| | 1237 | /* execute the command */ |
| | 1238 | executeAction(gActor, nil, gIssuingActor, nil, action); |
| | 1239 | } |
| | 1240 | |
| | 1241 | |
| | 1242 | /* ------------------------------------------------------------------------ */ |
| | 1243 | /* |
| | 1244 | * Run some code in a simulated Action environment. We'll create a dummy |
| | 1245 | * instance of the given Action class, and set up a command transcript, |
| | 1246 | * then invoke the function. This is useful for writing daemon code that |
| | 1247 | * needs to invoke other code that's set up to expect a normal action |
| | 1248 | * processing environment. |
| | 1249 | */ |
| | 1250 | withActionEnv(actionClass, actor, func) |
| | 1251 | { |
| | 1252 | local oldAction, oldActor; |
| | 1253 | |
| | 1254 | /* remember the old globals */ |
| | 1255 | oldAction = gAction; |
| | 1256 | oldActor = gActor; |
| | 1257 | |
| | 1258 | try |
| | 1259 | { |
| | 1260 | /* set up a dummy action */ |
| | 1261 | gAction = actionClass.createInstance(); |
| | 1262 | |
| | 1263 | /* use the player character as the actor */ |
| | 1264 | gActor = actor; |
| | 1265 | |
| | 1266 | /* |
| | 1267 | * execute the function with a command transcript active; obtain |
| | 1268 | * and return the return value of the function |
| | 1269 | */ |
| | 1270 | return withCommandTranscript(CommandTranscript, func); |
| | 1271 | } |
| | 1272 | finally |
| | 1273 | { |
| | 1274 | /* restore globals on the way out */ |
| | 1275 | gAction = oldAction; |
| | 1276 | gActor = oldActor; |
| | 1277 | } |
| | 1278 | } |
| | 1279 | |
| | 1280 | |
| | 1281 | /* ------------------------------------------------------------------------ */ |
| | 1282 | /* |
| | 1283 | * Exit signal. This signal indicates that we're finished with the |
| | 1284 | * entire command execution sequence for an action; the remainder of the |
| | 1285 | * command execution sequence is to be skipped for the action. Throw |
| | 1286 | * this from within the command execution sequence in order to skip |
| | 1287 | * directly to the end-of-turn processing. This skips everything |
| | 1288 | * remaining in the action, including after-action notification and the |
| | 1289 | * like. This signal skips directly past the 'afterAction' phase of the |
| | 1290 | * command. |
| | 1291 | * |
| | 1292 | * Note that this doesn't prevent further processing of the same command |
| | 1293 | * if there are multiple objects involved, and it doesn't affect |
| | 1294 | * processing of additional commands on the same command line. If you |
| | 1295 | * want to cancel further iteration of the same command for additional |
| | 1296 | * objects, call gAction.cancelIteration(). |
| | 1297 | */ |
| | 1298 | class ExitSignal: Exception |
| | 1299 | ; |
| | 1300 | |
| | 1301 | /* |
| | 1302 | * Exit Action signal. This signal indicates that we're finished with |
| | 1303 | * the execAction portion of processing the command, but we still want |
| | 1304 | * to proceed with the rest of the command as normal. This can be used |
| | 1305 | * when a step in the action processing wants to preempt any of the more |
| | 1306 | * default processing that would normally follow. This skips directly |
| | 1307 | * to the 'afterAction' phase of the command. |
| | 1308 | * |
| | 1309 | * Note that this doesn't prevent further processing of the same command |
| | 1310 | * if there are multiple objects involved, and it doesn't affect |
| | 1311 | * processing of additional commands on the same command line. If you |
| | 1312 | * want to cancel further iteration of the same command for additional |
| | 1313 | * objects, call gAction.cancelIteration(). |
| | 1314 | */ |
| | 1315 | class ExitActionSignal: Exception |
| | 1316 | ; |
| | 1317 | |
| | 1318 | /* |
| | 1319 | * Abort implicit command signal. This exception indicates that we are |
| | 1320 | * aborting an implicit command without having tried to execute the |
| | 1321 | * command at all. This is thrown when an implied command is to be |
| | 1322 | * aborted before it's even attempted, such as when verification shows |
| | 1323 | * the command is obviously dangerous and thus should never be attempted |
| | 1324 | * without the player having explicitly requesting it. |
| | 1325 | */ |
| | 1326 | class AbortImplicitSignal: Exception |
| | 1327 | ; |
| | 1328 | |
| | 1329 | /* ------------------------------------------------------------------------ */ |
| | 1330 | /* |
| | 1331 | * Action Remap signal. This signal can be thrown only during the noun |
| | 1332 | * phrase resolution phase of execution, and indicates that we want to |
| | 1333 | * remap the action to a different action, specified in the signal. |
| | 1334 | * |
| | 1335 | * This is useful when an object is always used in a special way, so |
| | 1336 | * that a generic verb used with the object must be mapped to a more |
| | 1337 | * specific verb on the object. For example, a game with a generic USE |
| | 1338 | * verb might convert USE PAINTBRUSH ON WALL to PAINT WALL WITH |
| | 1339 | * PAINTBRUSH by remapping the UseWith action to a PaintWith action |
| | 1340 | * instead. |
| | 1341 | */ |
| | 1342 | class RemapActionSignal: Exception |
| | 1343 | construct(action) |
| | 1344 | { |
| | 1345 | /* remember the new action */ |
| | 1346 | action_ = action; |
| | 1347 | } |
| | 1348 | |
| | 1349 | /* the new action that should replace the original action */ |
| | 1350 | action_ = nil |
| | 1351 | ; |
| | 1352 | |
| | 1353 | /* |
| | 1354 | * Remap a 'verify' method for a remapped action. This is normally |
| | 1355 | * invoked through the remapTo() macro. |
| | 1356 | */ |
| | 1357 | remapVerify(oldRole, resultSoFar, remapInfo) |
| | 1358 | { |
| | 1359 | local newAction; |
| | 1360 | local objs; |
| | 1361 | local idx; |
| | 1362 | local newRole; |
| | 1363 | |
| | 1364 | /* extract new action's object list from the remapping info list */ |
| | 1365 | objs = remapInfo.sublist(2); |
| | 1366 | |
| | 1367 | /* |
| | 1368 | * Create a new action object. We only perform verification |
| | 1369 | * remapping during the resolution phase of the command processing, |
| | 1370 | * because once we've finished resolving, we'll actually replace the |
| | 1371 | * action with the remapped action and thus won't have to remap |
| | 1372 | * verification (or anything else) at that point. So, pass true for |
| | 1373 | * the in-resolve flag to the action creation routine. |
| | 1374 | */ |
| | 1375 | newAction = remapActionCreate(true, oldRole, remapInfo); |
| | 1376 | |
| | 1377 | /* |
| | 1378 | * Find the object that's given as a resolved object, rather than as |
| | 1379 | * a DirectObject (etc) identifier - the one given as a specific |
| | 1380 | * object is the one that corresponds to the original object. |
| | 1381 | */ |
| | 1382 | idx = objs.indexWhich({x: dataType(x) == TypeObject}); |
| | 1383 | |
| | 1384 | /* get the role identifier (DirectObject, etc) for the slot position */ |
| | 1385 | newRole = newAction.getRoleFromIndex(idx); |
| | 1386 | |
| | 1387 | /* if we don't yet have a result list object, create one */ |
| | 1388 | if (resultSoFar == nil) |
| | 1389 | resultSoFar = new VerifyResultList(); |
| | 1390 | |
| | 1391 | /* if we found a remapping, verify it */ |
| | 1392 | if (idx != nil) |
| | 1393 | { |
| | 1394 | /* |
| | 1395 | * Remember the remapped object in the result list. Note that we |
| | 1396 | * do this first, before calling the remapped verification |
| | 1397 | * property, so that our call to the remapped verification |
| | 1398 | * property will overwrite this setting if it does further |
| | 1399 | * remapping. We want the ultimate target object represented |
| | 1400 | * here, after all remappings are finished. |
| | 1401 | */ |
| | 1402 | resultSoFar.remapAction_ = newAction; |
| | 1403 | resultSoFar.remapTarget_ = objs[idx]; |
| | 1404 | resultSoFar.remapRole_ = newRole; |
| | 1405 | |
| | 1406 | /* install the new action as the current action while verifying */ |
| | 1407 | local oldAction = gAction; |
| | 1408 | gAction = newAction; |
| | 1409 | |
| | 1410 | try |
| | 1411 | { |
| | 1412 | /* call verification on the new object in the new role */ |
| | 1413 | return newAction.callVerifyProp( |
| | 1414 | objs[idx], |
| | 1415 | newAction.getVerifyPropForRole(newRole), |
| | 1416 | newAction.getPreCondPropForRole(newRole), |
| | 1417 | newAction.getRemapPropForRole(newRole), |
| | 1418 | resultSoFar, newRole); |
| | 1419 | } |
| | 1420 | finally |
| | 1421 | { |
| | 1422 | /* restore the old gAction on the way out */ |
| | 1423 | gAction = oldAction; |
| | 1424 | } |
| | 1425 | } |
| | 1426 | else |
| | 1427 | { |
| | 1428 | /* there's no remapping, so there's nothing to verify */ |
| | 1429 | return resultSoFar; |
| | 1430 | } |
| | 1431 | } |
| | 1432 | |
| | 1433 | /* |
| | 1434 | * Perform a remapping to a new action. This is normally invoked |
| | 1435 | * through the remapTo() macro. |
| | 1436 | */ |
| | 1437 | remapAction(inResolve, oldRole, remapInfo) |
| | 1438 | { |
| | 1439 | local newAction; |
| | 1440 | |
| | 1441 | /* get the new action */ |
| | 1442 | newAction = remapActionCreate(inResolve, oldRole, remapInfo); |
| | 1443 | |
| | 1444 | /* |
| | 1445 | * replace the current action, using the appropriate mechanism |
| | 1446 | * depending on the current processing phase |
| | 1447 | */ |
| | 1448 | if (inResolve) |
| | 1449 | { |
| | 1450 | /* |
| | 1451 | * we're still resolving the objects, so we must use a signal to |
| | 1452 | * start the resolution process over for the new action |
| | 1453 | */ |
| | 1454 | throw new RemapActionSignal(newAction); |
| | 1455 | } |
| | 1456 | else |
| | 1457 | { |
| | 1458 | /* |
| | 1459 | * We've finished resolving everything, so we can simply use the |
| | 1460 | * new action as a replacement action. |
| | 1461 | */ |
| | 1462 | execNestedAction(true, true, gActor, newAction); |
| | 1463 | |
| | 1464 | /* |
| | 1465 | * the remapped action replaces the original action, so |
| | 1466 | * terminate the original action |
| | 1467 | */ |
| | 1468 | exit; |
| | 1469 | } |
| | 1470 | } |
| | 1471 | |
| | 1472 | /* |
| | 1473 | * Create a new action object for the given remapped action. |
| | 1474 | */ |
| | 1475 | remapActionCreate(inResolve, oldRole, remapInfo) |
| | 1476 | { |
| | 1477 | local newAction; |
| | 1478 | local newObjs; |
| | 1479 | local newActionClass; |
| | 1480 | local objs; |
| | 1481 | |
| | 1482 | /* get the new action class and object list from the remap info */ |
| | 1483 | newActionClass = remapInfo[1]; |
| | 1484 | objs = remapInfo.sublist(2); |
| | 1485 | |
| | 1486 | /* |
| | 1487 | * create a new instance of the replacement action, carrying forward |
| | 1488 | * the properties of the original (current) action |
| | 1489 | */ |
| | 1490 | newAction = newActionClass.createActionFrom(gAction); |
| | 1491 | |
| | 1492 | /* remember the original action we're remapping */ |
| | 1493 | newAction.setOriginalAction(gAction); |
| | 1494 | |
| | 1495 | /* set up an empty vector for the match trees for the new action */ |
| | 1496 | newObjs = new Vector(objs.length()); |
| | 1497 | |
| | 1498 | /* remap according to the phase of the execution */ |
| | 1499 | if (inResolve) |
| | 1500 | { |
| | 1501 | /* translate the object mappings */ |
| | 1502 | foreach (local cur in objs) |
| | 1503 | { |
| | 1504 | /* check what we have to translate */ |
| | 1505 | if (dataType(cur) == TypeEnum) |
| | 1506 | { |
| | 1507 | /* |
| | 1508 | * it's an object role - if it's the special OtherObject |
| | 1509 | * designator, get the other role of a two-object |
| | 1510 | * command |
| | 1511 | */ |
| | 1512 | if (cur == OtherObject) |
| | 1513 | cur = gAction.getOtherObjectRole(oldRole); |
| | 1514 | |
| | 1515 | /* |
| | 1516 | * get the match tree for this role from the old action |
| | 1517 | * and add it to our list |
| | 1518 | */ |
| | 1519 | newObjs.append(gAction.getMatchForRole(cur)); |
| | 1520 | } |
| | 1521 | else |
| | 1522 | { |
| | 1523 | /* append the new ResolveInfo to the new object list */ |
| | 1524 | newObjs.append(gAction.getResolveInfo(cur, oldRole)); |
| | 1525 | } |
| | 1526 | } |
| | 1527 | |
| | 1528 | /* set the object matches in the new action */ |
| | 1529 | newAction.setObjectMatches(newObjs.toList()...); |
| | 1530 | } |
| | 1531 | else |
| | 1532 | { |
| | 1533 | /* translate the object mappings */ |
| | 1534 | foreach (local cur in objs) |
| | 1535 | { |
| | 1536 | /* check what we have to translate */ |
| | 1537 | if (dataType(cur) != TypeEnum) |
| | 1538 | { |
| | 1539 | /* it's an explicit object - use it directly */ |
| | 1540 | newObjs.append(cur); |
| | 1541 | } |
| | 1542 | else |
| | 1543 | { |
| | 1544 | /* it's a role - translate OtherObject if needed */ |
| | 1545 | if (cur == OtherObject) |
| | 1546 | cur = gAction.getOtherObjectRole(oldRole); |
| | 1547 | |
| | 1548 | /* get the resolved object for this role */ |
| | 1549 | newObjs.append(gAction.getObjectForRole(cur)); |
| | 1550 | } |
| | 1551 | } |
| | 1552 | |
| | 1553 | /* set the resolved objects in the new action */ |
| | 1554 | newAction.setResolvedObjects(newObjs.toList()...); |
| | 1555 | } |
| | 1556 | |
| | 1557 | /* return the new action */ |
| | 1558 | return newAction; |
| | 1559 | } |
| | 1560 | |
| | 1561 | /* ------------------------------------------------------------------------ */ |
| | 1562 | /* |
| | 1563 | * Result message object. This is used for verification results and |
| | 1564 | * main command reports, which must keep track of messages to display. |
| | 1565 | */ |
| | 1566 | class MessageResult: object |
| | 1567 | /* |
| | 1568 | * Construct given literal message text, or alternatively a property |
| | 1569 | * of the current actor's verb messages object. In either case, |
| | 1570 | * we'll expand the message immediately to allow the message to be |
| | 1571 | * displayed later with any parameters fixed at the time the message |
| | 1572 | * is constructed. |
| | 1573 | */ |
| | 1574 | construct(msg, [params]) |
| | 1575 | { |
| | 1576 | /* if we're based on an existing object, copy its characteristics */ |
| | 1577 | if (dataType(msg) == TypeObject && msg.ofKind(MessageResult)) |
| | 1578 | { |
| | 1579 | /* base it on the existing object */ |
| | 1580 | messageText_ = msg.messageText_; |
| | 1581 | messageProp_ = msg.messageProp_; |
| | 1582 | return; |
| | 1583 | } |
| | 1584 | |
| | 1585 | /* |
| | 1586 | * if the message was given as a property, remember the property |
| | 1587 | * for identification purposes |
| | 1588 | */ |
| | 1589 | if (dataType(msg) == TypeProp) |
| | 1590 | messageProp_ = msg; |
| | 1591 | |
| | 1592 | /* |
| | 1593 | * Resolve the message and store the text. Use the action's |
| | 1594 | * objects (the direct object, indirect object, etc) as the |
| | 1595 | * sources for message overrides - this makes it easy to override |
| | 1596 | * messages on a per-object basis without having to rewrite the |
| | 1597 | * whole verify/check/action routines. |
| | 1598 | */ |
| | 1599 | messageText_ = resolveMessageText(gAction.getCurrentObjects(), |
| | 1600 | msg, params); |
| | 1601 | } |
| | 1602 | |
| | 1603 | /* |
| | 1604 | * Static method: resolve a message. If the message is given as a |
| | 1605 | * property, we'll look up the message in the given source objects |
| | 1606 | * and in the actor's "action messages" object. We'll return the |
| | 1607 | * resolved message string. |
| | 1608 | */ |
| | 1609 | resolveMessageText(sources, msg, params) |
| | 1610 | { |
| | 1611 | /* |
| | 1612 | * The message can be given either as a string or as a property |
| | 1613 | * of the actor's verb message object. If it's the latter, look |
| | 1614 | * up the text of the property from the appropriate object. |
| | 1615 | */ |
| | 1616 | findTextSource: |
| | 1617 | if (dataType(msg) == TypeProp) |
| | 1618 | { |
| | 1619 | local msgObj; |
| | 1620 | |
| | 1621 | /* |
| | 1622 | * Presume that we'll read the message from the current |
| | 1623 | * actor's "action message object." This is typically |
| | 1624 | * playerActionMessages or npcActionMessages, but it's up to |
| | 1625 | * the actor to specify which object we get our messages |
| | 1626 | * from. |
| | 1627 | */ |
| | 1628 | msgObj = gActor.getActionMessageObj(); |
| | 1629 | |
| | 1630 | /* |
| | 1631 | * First, look up the message property in the action's |
| | 1632 | * objects (the direct object, indirect object, etc). This |
| | 1633 | * makes it easy to override messages on a per-object basis |
| | 1634 | * without having to rewrite the whole verify/check/action |
| | 1635 | * routine. |
| | 1636 | */ |
| | 1637 | foreach (local cur in sources) |
| | 1638 | { |
| | 1639 | /* check to see if this object defines the message property */ |
| | 1640 | if (cur != nil && cur.propDefined(msg)) |
| | 1641 | { |
| | 1642 | local res; |
| | 1643 | |
| | 1644 | /* |
| | 1645 | * This object defines the property, so check what |
| | 1646 | * we have. |
| | 1647 | */ |
| | 1648 | switch (cur.propType(msg)) |
| | 1649 | { |
| | 1650 | case TypeProp: |
| | 1651 | /* |
| | 1652 | * It's another property, so we're being |
| | 1653 | * directed back to the player action message |
| | 1654 | * object. The object does override the |
| | 1655 | * message, but the override points to another |
| | 1656 | * message property in the action object message |
| | 1657 | * set. Simply redirect 'msg' to point to the |
| | 1658 | * new property, and use the same action message |
| | 1659 | * object we already assumed we'd use. |
| | 1660 | */ |
| | 1661 | msg = cur.(msg); |
| | 1662 | break; |
| | 1663 | |
| | 1664 | case TypeSString: |
| | 1665 | /* it's a simple string - retrieve it */ |
| | 1666 | msg = cur.(msg); |
| | 1667 | |
| | 1668 | /* |
| | 1669 | * since it's just a string, we're done finding |
| | 1670 | * the message text - there's no need to do any |
| | 1671 | * further property lookup, since we've obviously |
| | 1672 | * reached the end of that particular line |
| | 1673 | */ |
| | 1674 | break findTextSource; |
| | 1675 | |
| | 1676 | case TypeCode: |
| | 1677 | /* |
| | 1678 | * Check the parameter count - we'll allow this |
| | 1679 | * method to take the full set of parameters, or |
| | 1680 | * no parameters at all. We allow the no-param |
| | 1681 | * case for convenience in cases where the method |
| | 1682 | * simply wants to return a string or property ID |
| | 1683 | * from a short method that doesn't need to know |
| | 1684 | * the parameters; in these cases, it's |
| | 1685 | * syntactically a lot nicer looking to write it |
| | 1686 | * as a "prop = (expresion)" than to write the |
| | 1687 | * full method-with-params syntax. |
| | 1688 | */ |
| | 1689 | if (cur.getPropParams(msg) == [0, 0, nil]) |
| | 1690 | res = cur.(msg); |
| | 1691 | else |
| | 1692 | res = cur.(msg)(params...); |
| | 1693 | |
| | 1694 | /* |
| | 1695 | * If that returned nil, ignore it entirely and |
| | 1696 | * keep scanning the remaining source objects. |
| | 1697 | * The object must have decided it didn't want to |
| | 1698 | * provide the message override in this case |
| | 1699 | * after all. |
| | 1700 | */ |
| | 1701 | if (res == nil) |
| | 1702 | continue; |
| | 1703 | |
| | 1704 | /* we didn't get nil, so use the result */ |
| | 1705 | msg = res; |
| | 1706 | |
| | 1707 | /* |
| | 1708 | * if we got a string, we've fully resolved the |
| | 1709 | * message text, so we can stop searching for it |
| | 1710 | */ |
| | 1711 | if (dataType(msg) == TypeSString) |
| | 1712 | break findTextSource; |
| | 1713 | |
| | 1714 | /* |
| | 1715 | * It's not nil and it's not a string, so it must |
| | 1716 | * be a property ID. In this case, the property |
| | 1717 | * ID is a property to evaluate in the normal |
| | 1718 | * action message object. Simply proceed to |
| | 1719 | * evaluate the new message property as normal. |
| | 1720 | */ |
| | 1721 | break; |
| | 1722 | |
| | 1723 | case TypeNil: |
| | 1724 | /* |
| | 1725 | * it's explicitly nil, which simply means to |
| | 1726 | * ignore this definition; keep scanning other |
| | 1727 | * source objects |
| | 1728 | */ |
| | 1729 | continue; |
| | 1730 | |
| | 1731 | default: |
| | 1732 | /* |
| | 1733 | * In any other case, this must simply be the |
| | 1734 | * message we're to use. For this case, the |
| | 1735 | * source of the message is this object, so |
| | 1736 | * forget about the normal action message object |
| | 1737 | * and instead use the current object. Then |
| | 1738 | * proceed to evaluate the message property as |
| | 1739 | * normal, which will fetch it from the current |
| | 1740 | * object. |
| | 1741 | */ |
| | 1742 | msgObj = cur; |
| | 1743 | break; |
| | 1744 | } |
| | 1745 | |
| | 1746 | /* |
| | 1747 | * we found a definition, so we don't need to look |
| | 1748 | * at any of the other objects involved in the |
| | 1749 | * action - we just use the first override we find |
| | 1750 | */ |
| | 1751 | break; |
| | 1752 | } |
| | 1753 | } |
| | 1754 | |
| | 1755 | /* look up the message in the actor's message generator */ |
| | 1756 | msg = msgObj.(msg)(params...); |
| | 1757 | } |
| | 1758 | |
| | 1759 | /* |
| | 1760 | * format the string and remember the result - do the formatting |
| | 1761 | * immediately, because we want to make sure we expand any |
| | 1762 | * substitution parameters in the context of the current |
| | 1763 | * command, since the parameters might change (and thus alter |
| | 1764 | * the meaning of the message) by the time it's displayed |
| | 1765 | */ |
| | 1766 | msg = langMessageBuilder.generateMessage(msg); |
| | 1767 | |
| | 1768 | /* |
| | 1769 | * "quote" the message text - it's fully expanded now, so |
| | 1770 | * there's no need to further expand anything that might by |
| | 1771 | * coincidence look like substitution parameters in its text |
| | 1772 | */ |
| | 1773 | msg = langMessageBuilder.quoteMessage(msg); |
| | 1774 | |
| | 1775 | /* return the resolved message string */ |
| | 1776 | return msg; |
| | 1777 | } |
| | 1778 | |
| | 1779 | /* |
| | 1780 | * set a new message, given the same type of information as we'd use |
| | 1781 | * to construct the object |
| | 1782 | */ |
| | 1783 | setMessage(msg, [params]) |
| | 1784 | { |
| | 1785 | /* simply invoke the constructor to re-fill the message data */ |
| | 1786 | construct(msg, params...); |
| | 1787 | } |
| | 1788 | |
| | 1789 | /* |
| | 1790 | * Display a message describing why the command isn't allowed. |
| | 1791 | */ |
| | 1792 | showMessage() |
| | 1793 | { |
| | 1794 | /* show our message string */ |
| | 1795 | say(messageText_); |
| | 1796 | } |
| | 1797 | |
| | 1798 | /* the text of our result message */ |
| | 1799 | messageText_ = nil |
| | 1800 | |
| | 1801 | /* the message property, if we have one */ |
| | 1802 | messageProp_ = nil |
| | 1803 | ; |
| | 1804 | |