| | 1 | #charset "us-ascii" |
| | 2 | |
| | 3 | /* |
| | 4 | * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. |
| | 5 | * |
| | 6 | * TADS 3 Library: Actions. |
| | 7 | * |
| | 8 | * This module defines the set of built-in library actions. |
| | 9 | */ |
| | 10 | |
| | 11 | #include "adv3.h" |
| | 12 | #include "tok.h" |
| | 13 | |
| | 14 | /* ------------------------------------------------------------------------ */ |
| | 15 | /* |
| | 16 | * Special "debug" action - this simply breaks into the debugger, if the |
| | 17 | * debugger is present. |
| | 18 | */ |
| | 19 | DefineIAction(Debug) |
| | 20 | execAction() |
| | 21 | { |
| | 22 | /* if the debugger is present, break into it */ |
| | 23 | if (t3DebugTrace(T3DebugCheck)) |
| | 24 | t3DebugTrace(T3DebugBreak); |
| | 25 | else |
| | 26 | "Debugger not present. "; |
| | 27 | } |
| | 28 | ; |
| | 29 | |
| | 30 | /* ------------------------------------------------------------------------ */ |
| | 31 | /* |
| | 32 | * Special internal action to note a change to the darkness level. This |
| | 33 | * command is invoked internally when a change to the darkness level |
| | 34 | * occurs. |
| | 35 | */ |
| | 36 | DefineIAction(NoteDarkness) |
| | 37 | execAction() |
| | 38 | { |
| | 39 | /* |
| | 40 | * if we're in the dark, note that darkness has fallen; |
| | 41 | * otherwise, show the player character's room description as |
| | 42 | * though the player had typed "look" |
| | 43 | */ |
| | 44 | if (gActor.isLocationLit()) |
| | 45 | { |
| | 46 | /* look around */ |
| | 47 | gActor.lookAround(true); |
| | 48 | } |
| | 49 | else |
| | 50 | { |
| | 51 | /* it is now dark */ |
| | 52 | mainReport(&newlyDarkMsg); |
| | 53 | } |
| | 54 | } |
| | 55 | |
| | 56 | /* this is an internal command that takes no time */ |
| | 57 | actionTime = 0 |
| | 58 | |
| | 59 | /* this isn't a real action, so it's not repeatable */ |
| | 60 | isRepeatable = nil |
| | 61 | |
| | 62 | /* this action doesn't do anything; don't include it in undo */ |
| | 63 | includeInUndo = nil |
| | 64 | ; |
| | 65 | |
| | 66 | /* ------------------------------------------------------------------------ */ |
| | 67 | /* |
| | 68 | * Special "again" action. This command repeats the previous command. |
| | 69 | */ |
| | 70 | DefineIAction(Again) |
| | 71 | /* for obvious reasons, 'again' is not itself repeatable with 'again' */ |
| | 72 | isRepeatable = nil |
| | 73 | |
| | 74 | /* |
| | 75 | * the undo command itself is not undoable (but the underlying |
| | 76 | * command that we repeat might be) |
| | 77 | */ |
| | 78 | includeInUndo = nil |
| | 79 | |
| | 80 | /* information on the most recent command */ |
| | 81 | lastIssuingActor = nil |
| | 82 | lastTargetActor = nil |
| | 83 | lastTargetActorPhrase = nil |
| | 84 | lastAction = nil |
| | 85 | |
| | 86 | /* save the most recent command so that it can be repeated if desired */ |
| | 87 | saveForAgain(issuingActor, targetActor, targetActorPhrase, action) |
| | 88 | { |
| | 89 | /* save the information */ |
| | 90 | lastIssuingActor = issuingActor; |
| | 91 | lastTargetActor = targetActor; |
| | 92 | lastTargetActorPhrase = targetActorPhrase; |
| | 93 | lastAction = action.createClone(); |
| | 94 | } |
| | 95 | |
| | 96 | /* forget the last command, so that AGAIN cannot be used */ |
| | 97 | clearForAgain() { lastAction = nil; } |
| | 98 | |
| | 99 | /* |
| | 100 | * Execute the 'again' command. This action is special enough that |
| | 101 | * we override its entire action processing sequence - this is |
| | 102 | * necessary in case we're repeating another special command, such |
| | 103 | * as 'again', and in any case is desirable because we don't want |
| | 104 | * 'again' to count as a command in its own right; it's essentially |
| | 105 | * just a macro that we replace with the original command. |
| | 106 | */ |
| | 107 | doAction(issuingActor, targetActor, targetActorPhrase, |
| | 108 | countsAsIssuerTurn) |
| | 109 | { |
| | 110 | /* if there's nothing to repeat, show an error and give up */ |
| | 111 | if (lastAction == nil) |
| | 112 | { |
| | 113 | gLibMessages.noCommandForAgain(); |
| | 114 | return; |
| | 115 | } |
| | 116 | |
| | 117 | /* |
| | 118 | * 'again' cannot be executed with a target actor - the target |
| | 119 | * actor must be the player character |
| | 120 | */ |
| | 121 | if (!targetActor.isPlayerChar) |
| | 122 | { |
| | 123 | gLibMessages.againCannotChangeActor(); |
| | 124 | return; |
| | 125 | } |
| | 126 | |
| | 127 | /* |
| | 128 | * if the issuing actor isn't the same as the target actor, make |
| | 129 | * sure the issuer can still talk to the target |
| | 130 | */ |
| | 131 | if (lastIssuingActor != lastTargetActor |
| | 132 | && !lastIssuingActor.canTalkTo(lastTargetActor)) |
| | 133 | { |
| | 134 | /* complain that we can no longer talk to the target */ |
| | 135 | gLibMessages.againCannotTalkToTarget( |
| | 136 | lastIssuingActor, lastTargetActor); |
| | 137 | return; |
| | 138 | } |
| | 139 | |
| | 140 | /* |
| | 141 | * If the last issuing actor is different from the last target |
| | 142 | * actor, then the command counts as an issuer turn, because |
| | 143 | * we're effectively repeating the entire last command, including |
| | 144 | * the target actor specification. That is, after a command like |
| | 145 | * "bob, go east", saying "again" is just like saying "bob, go |
| | 146 | * east" again, which counts as an issuer turn. |
| | 147 | */ |
| | 148 | if (lastTargetActor != lastIssuingActor) |
| | 149 | countsAsIssuerTurn = true; |
| | 150 | |
| | 151 | /* reset any cached information for the new command context */ |
| | 152 | lastAction.resetAction(); |
| | 153 | |
| | 154 | /* repeat the action */ |
| | 155 | lastAction.repeatAction(lastTargetActor, lastTargetActorPhrase, |
| | 156 | lastIssuingActor, countsAsIssuerTurn); |
| | 157 | |
| | 158 | /* |
| | 159 | * If the command was directed from the issuer to a different |
| | 160 | * target actor, and the issuer wants to wait for the full set of |
| | 161 | * issued commands to complete before getting another turn, tell |
| | 162 | * the issuer to begin waiting. |
| | 163 | */ |
| | 164 | if (lastTargetActor != lastIssuingActor) |
| | 165 | lastIssuingActor.waitForIssuedCommand(lastTargetActor); |
| | 166 | } |
| | 167 | |
| | 168 | /* |
| | 169 | * this command itself consumes no time on the game clock (although |
| | 170 | * the action we perform might) |
| | 171 | */ |
| | 172 | actionTime = 0 |
| | 173 | ; |
| | 174 | |
| | 175 | /* ------------------------------------------------------------------------ */ |
| | 176 | /* |
| | 177 | * PreSaveObject - every instance of this class is notified, via its |
| | 178 | * execute() method, just before we save the game. This uses the |
| | 179 | * ModuleExecObject framework, so the sequencing lists (execBeforeMe, |
| | 180 | * execAfterMe) can be used to control relative ordering of execution |
| | 181 | * among instances. |
| | 182 | */ |
| | 183 | class PreSaveObject: ModuleExecObject |
| | 184 | /* |
| | 185 | * Each instance must override execute() with its specific pre-save |
| | 186 | * code. |
| | 187 | */ |
| | 188 | ; |
| | 189 | |
| | 190 | /* |
| | 191 | * PostRestoreObject - every instance of this class is notified, via its |
| | 192 | * execute() method, immediately after we restore the game. |
| | 193 | */ |
| | 194 | class PostRestoreObject: ModuleExecObject |
| | 195 | /* |
| | 196 | * note: each instance must override execute() with its post-restore |
| | 197 | * code |
| | 198 | */ |
| | 199 | |
| | 200 | /* |
| | 201 | * The "restore code," which is the (normally integer) value passed |
| | 202 | * as the second argument to restoreGame(). The restore code gives |
| | 203 | * us some idea of what triggered the restoration. By default, we |
| | 204 | * define the following restore codes: |
| | 205 | * |
| | 206 | * 1 - the system is restoring a game as part of interpreter |
| | 207 | * startup, usually because the user explicitly specified a game to |
| | 208 | * restore on the interpreter command line or via a GUI shell |
| | 209 | * mechanism, such as double-clicking on a saved game file from the |
| | 210 | * desktop. |
| | 211 | * |
| | 212 | * 2 - the user is explicitly restoring a game via a RESTORE command. |
| | 213 | * |
| | 214 | * Games and library extensions can use their own additional restore |
| | 215 | * codes in their calls to restoreGame(). |
| | 216 | */ |
| | 217 | restoreCode = nil |
| | 218 | ; |
| | 219 | |
| | 220 | /* |
| | 221 | * PreRestartObject - every instance of this class is notified, via its |
| | 222 | * execute() method, just before we restart the game (with a RESTART |
| | 223 | * command, for example). |
| | 224 | */ |
| | 225 | class PreRestartObject: ModuleExecObject |
| | 226 | /* |
| | 227 | * Each instance must override execute() with its specific |
| | 228 | * pre-restart code. |
| | 229 | */ |
| | 230 | ; |
| | 231 | |
| | 232 | /* |
| | 233 | * PostUndoObject - every instance of this class is notified, via its |
| | 234 | * execute() method, immediately after we perform an 'undo' command. |
| | 235 | */ |
| | 236 | class PostUndoObject: ModuleExecObject |
| | 237 | /* |
| | 238 | * Each instance must override execute() with its specific post-undo |
| | 239 | * code. |
| | 240 | */ |
| | 241 | ; |
| | 242 | |
| | 243 | /* ------------------------------------------------------------------------ */ |
| | 244 | /* |
| | 245 | * Special "save" action. This command saves the current game state to |
| | 246 | * an external file for later restoration. |
| | 247 | */ |
| | 248 | DefineSystemAction(Save) |
| | 249 | execSystemAction() |
| | 250 | { |
| | 251 | local result; |
| | 252 | local origElapsedTime; |
| | 253 | |
| | 254 | /* note the current elapsed game time */ |
| | 255 | origElapsedTime = realTimeManager.getElapsedTime(); |
| | 256 | |
| | 257 | /* ask for a file */ |
| | 258 | result = getInputFile(gLibMessages.getSavePrompt(), InFileSave, |
| | 259 | FileTypeT3Save, 0); |
| | 260 | |
| | 261 | /* check the inputFile response */ |
| | 262 | switch(result[1]) |
| | 263 | { |
| | 264 | case InFileSuccess: |
| | 265 | /* perform the save on the given file */ |
| | 266 | performSave(result[2]); |
| | 267 | |
| | 268 | /* done */ |
| | 269 | break; |
| | 270 | |
| | 271 | case InFileFailure: |
| | 272 | /* advise of the failure of the prompt */ |
| | 273 | gLibMessages.filePromptFailed(); |
| | 274 | break; |
| | 275 | |
| | 276 | case InFileCancel: |
| | 277 | /* acknowledge the cancellation */ |
| | 278 | gLibMessages.saveCanceled(); |
| | 279 | break; |
| | 280 | } |
| | 281 | |
| | 282 | /* |
| | 283 | * restore the original elapsed game time, so that the time spent |
| | 284 | * in the file selector dialog doesn't count against the game |
| | 285 | * time |
| | 286 | */ |
| | 287 | realTimeManager.setElapsedTime(origElapsedTime); |
| | 288 | } |
| | 289 | |
| | 290 | /* perform a save */ |
| | 291 | performSave(fname) |
| | 292 | { |
| | 293 | /* before saving the game, notify all PreSaveObject instances */ |
| | 294 | PreSaveObject.classExec(); |
| | 295 | |
| | 296 | /* |
| | 297 | * Save the game to the given file. If an error occurs, the |
| | 298 | * save routine will throw a runtime error. |
| | 299 | */ |
| | 300 | try |
| | 301 | { |
| | 302 | /* try saving the game */ |
| | 303 | saveGame(fname); |
| | 304 | } |
| | 305 | catch (RuntimeError err) |
| | 306 | { |
| | 307 | /* the save failed - mention the problem */ |
| | 308 | gLibMessages.saveFailed(err); |
| | 309 | |
| | 310 | /* done */ |
| | 311 | return; |
| | 312 | } |
| | 313 | |
| | 314 | /* note the successful save */ |
| | 315 | gLibMessages.saveOkay(); |
| | 316 | } |
| | 317 | |
| | 318 | /* |
| | 319 | * Saving has no effect on game state, so it's irrelevant whether or |
| | 320 | * not it's undoable; but it might be confusing to say we undid a |
| | 321 | * "save" command, because the player might think we deleted the |
| | 322 | * saved file. To avoid such confusion, do not include "save" |
| | 323 | * commands in the undo log. |
| | 324 | */ |
| | 325 | includeInUndo = nil |
| | 326 | |
| | 327 | /* |
| | 328 | * Don't allow this to be repeated with AGAIN. There's no point in |
| | 329 | * repeating a SAVE immediately, as nothing will have changed in the |
| | 330 | * game state to warrant saving again. |
| | 331 | */ |
| | 332 | isRepeatable = nil |
| | 333 | ; |
| | 334 | |
| | 335 | /* |
| | 336 | * Subclass of Save action that takes a literal string as part of the |
| | 337 | * command. The filename must be a literal enclosed in quotes, and the |
| | 338 | * string (with the quotes) must be stored in our fname_ property by |
| | 339 | * assignment of a quotedStringPhrase production in the grammar rule. |
| | 340 | */ |
| | 341 | DefineAction(SaveString, SaveAction) |
| | 342 | execSystemAction() |
| | 343 | { |
| | 344 | /* |
| | 345 | * Perform the save, using the filename given in our fname_ |
| | 346 | * parameter, trimmed of quotes. |
| | 347 | */ |
| | 348 | performSave(fname_.getStringText()); |
| | 349 | } |
| | 350 | ; |
| | 351 | |
| | 352 | |
| | 353 | /* ------------------------------------------------------------------------ */ |
| | 354 | /* |
| | 355 | * Special "restore" action. This action restores game state previously |
| | 356 | * saved with the "save" action. |
| | 357 | */ |
| | 358 | DefineSystemAction(Restore) |
| | 359 | execSystemAction() |
| | 360 | { |
| | 361 | /* ask for a file and restore it */ |
| | 362 | askAndRestore(); |
| | 363 | |
| | 364 | /* |
| | 365 | * regardless of what happened, abandon any additional commands |
| | 366 | * on the same command line |
| | 367 | */ |
| | 368 | throw new TerminateCommandException(); |
| | 369 | } |
| | 370 | |
| | 371 | /* |
| | 372 | * Ask for a file and try to restore it. Returns true on success, |
| | 373 | * nil on failure. (Failure could indicate that the user chose to |
| | 374 | * cancel out of the file selector, that we couldn't find the file to |
| | 375 | * restore, or that the file isn't a valid saved state file. In any |
| | 376 | * case, we show an appropriate message on failure.) |
| | 377 | */ |
| | 378 | askAndRestore() |
| | 379 | { |
| | 380 | local succ; |
| | 381 | local result; |
| | 382 | local origElapsedTime; |
| | 383 | |
| | 384 | /* presume failure */ |
| | 385 | succ = nil; |
| | 386 | |
| | 387 | /* note the current elapsed game time */ |
| | 388 | origElapsedTime = realTimeManager.getElapsedTime(); |
| | 389 | |
| | 390 | /* ask for a file */ |
| | 391 | result = getInputFile(gLibMessages.getRestorePrompt(), InFileOpen, |
| | 392 | FileTypeT3Save, 0); |
| | 393 | |
| | 394 | /* |
| | 395 | * restore the real-time clock, so that the time spent in the |
| | 396 | * file selector dialog doesn't count against the game time |
| | 397 | */ |
| | 398 | realTimeManager.setElapsedTime(origElapsedTime); |
| | 399 | |
| | 400 | /* check the inputFile response */ |
| | 401 | switch(result[1]) |
| | 402 | { |
| | 403 | case InFileSuccess: |
| | 404 | /* |
| | 405 | * try restoring the file; use code 2 to indicate that the |
| | 406 | * restoration was performed by an explicit RESTORE command |
| | 407 | */ |
| | 408 | if (performRestore(result[2], 2)) |
| | 409 | { |
| | 410 | /* note that we succeeded */ |
| | 411 | succ = true; |
| | 412 | } |
| | 413 | else |
| | 414 | { |
| | 415 | /* |
| | 416 | * failed - in case the failed restore took some time, |
| | 417 | * restore the real-time clock, so that the file-reading |
| | 418 | * time doesn't count against the game time |
| | 419 | */ |
| | 420 | realTimeManager.setElapsedTime(origElapsedTime); |
| | 421 | } |
| | 422 | |
| | 423 | /* done */ |
| | 424 | break; |
| | 425 | |
| | 426 | case InFileFailure: |
| | 427 | /* advise of the failure of the prompt */ |
| | 428 | gLibMessages.filePromptFailed(); |
| | 429 | break; |
| | 430 | |
| | 431 | case InFileCancel: |
| | 432 | /* acknowledge the cancellation */ |
| | 433 | gLibMessages.restoreCanceled(); |
| | 434 | break; |
| | 435 | } |
| | 436 | |
| | 437 | /* |
| | 438 | * If we were successful, clear out the AGAIN memory. This |
| | 439 | * avoids any confusion about whether we're repeating the RESTORE |
| | 440 | * command itself, the command just before RESTORE from the |
| | 441 | * current session, or the last command before SAVE from the |
| | 442 | * restored game. |
| | 443 | */ |
| | 444 | if (succ) |
| | 445 | AgainAction.clearForAgain(); |
| | 446 | |
| | 447 | /* return the success/failure indication */ |
| | 448 | return succ; |
| | 449 | } |
| | 450 | |
| | 451 | /* |
| | 452 | * Restore a game on startup. This can be called from mainRestore() |
| | 453 | * to restore a saved game directly as part of loading the game. |
| | 454 | * (Most interpreters provide a way of starting the interpreter |
| | 455 | * directly with a saved game to be restored, skipping the |
| | 456 | * intermediate step of running the game and using a RESTORE |
| | 457 | * command.) |
| | 458 | * |
| | 459 | * Returns true on success, nil on failure. On failure, the caller |
| | 460 | * should simply exit the program. On success, the caller should |
| | 461 | * start the game running, usually using runGame(), after showing any |
| | 462 | * desired introductory messages. |
| | 463 | */ |
| | 464 | startupRestore(fname) |
| | 465 | { |
| | 466 | /* |
| | 467 | * try restoring the game, using code 1 to indicate that this is |
| | 468 | * a direct startup restore |
| | 469 | */ |
| | 470 | if (performRestore(fname, 1)) |
| | 471 | { |
| | 472 | /* success - tell the caller to proceed with the restored game */ |
| | 473 | return true; |
| | 474 | } |
| | 475 | else |
| | 476 | { |
| | 477 | /* |
| | 478 | * Failure. We've described the problem, so ask the user |
| | 479 | * what they want to do about it. |
| | 480 | */ |
| | 481 | try |
| | 482 | { |
| | 483 | /* show options and read the response */ |
| | 484 | failedRestoreOptions(); |
| | 485 | |
| | 486 | /* if we get here, proceed with the game */ |
| | 487 | return true; |
| | 488 | } |
| | 489 | catch (QuittingException qe) |
| | 490 | { |
| | 491 | /* quitting - tell the caller to terminate */ |
| | 492 | return nil; |
| | 493 | } |
| | 494 | } |
| | 495 | } |
| | 496 | |
| | 497 | |
| | 498 | /* |
| | 499 | * Restore a file. 'code' is the restoreCode value for the |
| | 500 | * PostRestoreObject notifications. Returns true on success, nil on |
| | 501 | * failure. |
| | 502 | */ |
| | 503 | performRestore(fname, code) |
| | 504 | { |
| | 505 | try |
| | 506 | { |
| | 507 | /* restore the file */ |
| | 508 | restoreGame(fname); |
| | 509 | } |
| | 510 | catch (RuntimeError err) |
| | 511 | { |
| | 512 | /* failed - check the error to see what went wrong */ |
| | 513 | switch(err.errno_) |
| | 514 | { |
| | 515 | case 1201: |
| | 516 | /* not a saved state file */ |
| | 517 | gLibMessages.restoreInvalidFile(); |
| | 518 | break; |
| | 519 | |
| | 520 | case 1202: |
| | 521 | /* saved by different game or different version */ |
| | 522 | gLibMessages.restoreInvalidMatch(); |
| | 523 | break; |
| | 524 | |
| | 525 | case 1207: |
| | 526 | /* corrupted saved state file */ |
| | 527 | gLibMessages.restoreCorruptedFile(); |
| | 528 | break; |
| | 529 | |
| | 530 | default: |
| | 531 | /* some other failure */ |
| | 532 | gLibMessages.restoreFailed(err); |
| | 533 | break; |
| | 534 | } |
| | 535 | |
| | 536 | /* indicate failure */ |
| | 537 | return nil; |
| | 538 | } |
| | 539 | |
| | 540 | /* note that we've successfully restored the game */ |
| | 541 | gLibMessages.restoreOkay(); |
| | 542 | |
| | 543 | /* set the appropriate restore-action code */ |
| | 544 | PostRestoreObject.restoreCode = code; |
| | 545 | |
| | 546 | /* notify all PostRestoreObject instances */ |
| | 547 | PostRestoreObject.classExec(); |
| | 548 | |
| | 549 | /* |
| | 550 | * look around, to refresh the player's memory of the state the |
| | 551 | * game was in when saved |
| | 552 | */ |
| | 553 | "\b"; |
| | 554 | libGlobal.playerChar.lookAround(true); |
| | 555 | |
| | 556 | /* indicate success */ |
| | 557 | return true; |
| | 558 | } |
| | 559 | |
| | 560 | /* |
| | 561 | * There's no point in including this in undo. If the command |
| | 562 | * succeeds, it's not undoable itself, and there won't be any undo |
| | 563 | * information in the newly restored state. If the command fails, it |
| | 564 | * won't make any changes to the game state, so there won't be |
| | 565 | * anything to undo. |
| | 566 | */ |
| | 567 | includeInUndo = nil |
| | 568 | ; |
| | 569 | |
| | 570 | /* |
| | 571 | * Subclass of Restore action that takes a literal string as part of the |
| | 572 | * command. The filename must be a literal enclosed in quotes, and the |
| | 573 | * string (with the quotes) must be stored in our fname_ property by |
| | 574 | * assignment of a quotedStringPhrase production in the grammar rule. |
| | 575 | */ |
| | 576 | DefineAction(RestoreString, RestoreAction) |
| | 577 | execSystemAction() |
| | 578 | { |
| | 579 | /* |
| | 580 | * Perform the restore, using the filename given in our fname_ |
| | 581 | * parameter, trimmed of quotes. Use code 2, the same as any |
| | 582 | * other explicit RESTORE command. |
| | 583 | */ |
| | 584 | performRestore(fname_.getStringText(), 2); |
| | 585 | |
| | 586 | /* abandon any additional commands on the same command line */ |
| | 587 | throw new TerminateCommandException(); |
| | 588 | } |
| | 589 | ; |
| | 590 | |
| | 591 | /* ------------------------------------------------------------------------ */ |
| | 592 | /* |
| | 593 | * Restart the game from the beginning. |
| | 594 | */ |
| | 595 | DefineSystemAction(Restart) |
| | 596 | execSystemAction() |
| | 597 | { |
| | 598 | /* confirm that they really want to restart */ |
| | 599 | gLibMessages.confirmRestart(); |
| | 600 | if (yesOrNo()) |
| | 601 | { |
| | 602 | /* |
| | 603 | * The confirmation input will have put us into |
| | 604 | * start-of-command mode for sequencing purposes; force the |
| | 605 | * sequencer back to mid-command mode, so we can show |
| | 606 | * inter-command separation before the restart. |
| | 607 | */ |
| | 608 | |
| | 609 | /* restart the game */ |
| | 610 | doRestartGame(); |
| | 611 | } |
| | 612 | else |
| | 613 | { |
| | 614 | /* confirm that we're not really restarting */ |
| | 615 | gLibMessages.notRestarting(); |
| | 616 | } |
| | 617 | } |
| | 618 | |
| | 619 | /* carry out the restart action */ |
| | 620 | doRestartGame() |
| | 621 | { |
| | 622 | /* |
| | 623 | * Show a command separator, to provide separation from any |
| | 624 | * introductory text that we'll show on restarting. Note that |
| | 625 | * we probably just asked for confirmation, which means that the |
| | 626 | * command sequencer will be in start-of-command mode; force it |
| | 627 | * back to mid-command mode so we show inter-command separation. |
| | 628 | */ |
| | 629 | commandSequencer.setCommandMode(); |
| | 630 | "<.commandsep>"; |
| | 631 | |
| | 632 | /* before restarting, notify anyone interested of our intentions */ |
| | 633 | PreRestartObject.classExec(); |
| | 634 | |
| | 635 | /* |
| | 636 | * Throw a 'restart' signal; the main entrypoint loop will catch |
| | 637 | * this and actually perform the restart. |
| | 638 | * |
| | 639 | * Note that we *could* do the VM reset (via restartGame()) here, |
| | 640 | * but there's an advantage to doing it in the main loop: we |
| | 641 | * won't be in the stack context of whatever command we're |
| | 642 | * performing. If we did the restart here, it's possible that |
| | 643 | * some useless objects would survive the VM reset just because |
| | 644 | * they're referenced from within a caller's stack frame. Those |
| | 645 | * objects would immediately go out of scope when we get back to |
| | 646 | * the main loop, but they might survive long enough to create |
| | 647 | * apparent inconsistencies. In particular, if we did a |
| | 648 | * firstObj/nextObj loop, we could discover those objects and |
| | 649 | * re-establish more lasting references to them, which we |
| | 650 | * certainly don't want to do. By deferring the VM reset until |
| | 651 | * we get back to the main loop, we'll ensure that objects won't |
| | 652 | * survive the reset just because they're on the stack |
| | 653 | * momentarily here. |
| | 654 | */ |
| | 655 | throw new RestartSignal(); |
| | 656 | } |
| | 657 | |
| | 658 | /* there's no point in including this in undo */ |
| | 659 | includeInUndo = nil |
| | 660 | ; |
| | 661 | |
| | 662 | /* ------------------------------------------------------------------------ */ |
| | 663 | /* |
| | 664 | * Undo one turn. |
| | 665 | */ |
| | 666 | DefineSystemAction(Undo) |
| | 667 | /* |
| | 668 | * "Undo" is so special that we must override the entire action |
| | 669 | * processing sequence. We do this because undoing will restore the |
| | 670 | * game state as of the previous savepoint, which would leave all |
| | 671 | * sorts of things unsynchronized in the normal action sequence. To |
| | 672 | * avoid problems, we simply leave out any other action processing |
| | 673 | * and perform the 'undo' directly. |
| | 674 | */ |
| | 675 | doAction(issuingActor, targetActor, targetActorPhrase, |
| | 676 | countsAsIssuerTurn) |
| | 677 | { |
| | 678 | /* |
| | 679 | * the player obviously knows about UNDO, so there's no need for |
| | 680 | * a tip about it |
| | 681 | */ |
| | 682 | undoTip.makeShown(); |
| | 683 | |
| | 684 | /* |
| | 685 | * don't allow this unless the player character is performing |
| | 686 | * the command directly |
| | 687 | */ |
| | 688 | if (!targetActor.isPlayerChar) |
| | 689 | { |
| | 690 | /* |
| | 691 | * tell them this command cannot be directed to another |
| | 692 | * actor, and give up |
| | 693 | */ |
| | 694 | gLibMessages.systemActionToNPC(); |
| | 695 | return; |
| | 696 | } |
| | 697 | |
| | 698 | /* perform the undo */ |
| | 699 | performUndo(true); |
| | 700 | } |
| | 701 | |
| | 702 | /* |
| | 703 | * Perform undo. Returns true if we were successful, nil if not. |
| | 704 | * |
| | 705 | * 'asCommand' indicates whether or not the undo is being performed |
| | 706 | * as an explicit command: if so, we'll save the UNDO command for use |
| | 707 | * in AGAIN. |
| | 708 | */ |
| | 709 | performUndo(asCommand) |
| | 710 | { |
| | 711 | /* try undoing to the previous savepoint */ |
| | 712 | if (undo()) |
| | 713 | { |
| | 714 | local oldActor; |
| | 715 | local oldIssuer; |
| | 716 | local oldAction; |
| | 717 | |
| | 718 | /* notify all PostUndoObject instances */ |
| | 719 | PostUndoObject.classExec(); |
| | 720 | |
| | 721 | /* set up the globals for the command */ |
| | 722 | oldActor = gActor; |
| | 723 | oldIssuer = gIssuingActor; |
| | 724 | oldAction = gAction; |
| | 725 | |
| | 726 | /* set the new globals */ |
| | 727 | gActor = gPlayerChar; |
| | 728 | gIssuingActor = gPlayerChar; |
| | 729 | gAction = self; |
| | 730 | |
| | 731 | /* make sure we reset globals on the way out */ |
| | 732 | try |
| | 733 | { |
| | 734 | /* success - mention what we did */ |
| | 735 | gLibMessages.undoOkay(libGlobal.lastActorForUndo, |
| | 736 | libGlobal.lastCommandForUndo); |
| | 737 | |
| | 738 | /* look around, to refresh the player's memory */ |
| | 739 | libGlobal.playerChar.lookAround(true); |
| | 740 | } |
| | 741 | finally |
| | 742 | { |
| | 743 | /* restore the parser globals to how we found them */ |
| | 744 | gActor = oldActor; |
| | 745 | gIssuingActor = oldIssuer; |
| | 746 | gAction = oldAction; |
| | 747 | } |
| | 748 | |
| | 749 | /* |
| | 750 | * if this was an explicit 'undo' command, save the command |
| | 751 | * to allow repeating it with 'again' |
| | 752 | */ |
| | 753 | if (asCommand) |
| | 754 | AgainAction.saveForAgain(gPlayerChar, gPlayerChar, nil, self); |
| | 755 | |
| | 756 | /* indicate success */ |
| | 757 | return true; |
| | 758 | } |
| | 759 | else |
| | 760 | { |
| | 761 | /* no more undo information available */ |
| | 762 | gLibMessages.undoFailed(); |
| | 763 | |
| | 764 | /* indicate failure */ |
| | 765 | return nil; |
| | 766 | } |
| | 767 | } |
| | 768 | |
| | 769 | /* |
| | 770 | * "undo" is not undoable - if we undo again after an undo, we undo |
| | 771 | * the next most recent command |
| | 772 | */ |
| | 773 | includeInUndo = nil |
| | 774 | ; |
| | 775 | |
| | 776 | /* ------------------------------------------------------------------------ */ |
| | 777 | /* |
| | 778 | * Save the defaults |
| | 779 | */ |
| | 780 | DefineSystemAction(SaveDefaults) |
| | 781 | execSystemAction() |
| | 782 | { |
| | 783 | /* tell SettingsItem to save all settings */ |
| | 784 | settingsUI.saveSettingsMsg(); |
| | 785 | } |
| | 786 | |
| | 787 | /* there's no point in including this in undo */ |
| | 788 | includeInUndo = nil |
| | 789 | ; |
| | 790 | |
| | 791 | /* |
| | 792 | * Restore defaults |
| | 793 | */ |
| | 794 | DefineSystemAction(RestoreDefaults) |
| | 795 | execSystemAction() |
| | 796 | { |
| | 797 | /* |
| | 798 | * Tell SettingsItem to restore all settings. This is an |
| | 799 | * explicit request, so we want SettingsItem to describe what |
| | 800 | * happened. |
| | 801 | */ |
| | 802 | settingsUI.restoreSettingsMsg(); |
| | 803 | } |
| | 804 | |
| | 805 | /* there's no point in including this in undo */ |
| | 806 | includeInUndo = nil |
| | 807 | ; |
| | 808 | |
| | 809 | |
| | 810 | /* ------------------------------------------------------------------------ */ |
| | 811 | /* |
| | 812 | * Quit the game. |
| | 813 | */ |
| | 814 | DefineSystemAction(Quit) |
| | 815 | execSystemAction() |
| | 816 | { |
| | 817 | /* confirm that they really want to quit */ |
| | 818 | gLibMessages.confirmQuit(); |
| | 819 | if (yesOrNo()) |
| | 820 | { |
| | 821 | /* carry out the termination */ |
| | 822 | terminateGame(); |
| | 823 | } |
| | 824 | else |
| | 825 | { |
| | 826 | /* show the confirmation that we're not quitting */ |
| | 827 | gLibMessages.notTerminating(); |
| | 828 | } |
| | 829 | } |
| | 830 | |
| | 831 | /* |
| | 832 | * Carry out game termination. This can be called when we wish to |
| | 833 | * end the game without asking for any additional player |
| | 834 | * confirmation. |
| | 835 | */ |
| | 836 | terminateGame() |
| | 837 | { |
| | 838 | /* acknowledge that we're quitting */ |
| | 839 | gLibMessages.okayQuitting(); |
| | 840 | |
| | 841 | /* throw a 'quitting' signal to end the game */ |
| | 842 | throw new QuittingException; |
| | 843 | } |
| | 844 | |
| | 845 | /* there's no point in including this in undo */ |
| | 846 | includeInUndo = nil |
| | 847 | ; |
| | 848 | |
| | 849 | /* |
| | 850 | * Pause the game. This stops the real-time clock until the user |
| | 851 | * presses a key. Games that don't use the real-time clock will have no |
| | 852 | * use for this. |
| | 853 | */ |
| | 854 | DefineSystemAction(Pause) |
| | 855 | execSystemAction() |
| | 856 | { |
| | 857 | local elapsed; |
| | 858 | |
| | 859 | /* |
| | 860 | * remember the current elapsed game real time - when we are |
| | 861 | * released from the pause, we'll restore this time |
| | 862 | */ |
| | 863 | elapsed = realTimeManager.getElapsedTime(); |
| | 864 | |
| | 865 | /* show our prompt */ |
| | 866 | gLibMessages.pausePrompt(); |
| | 867 | |
| | 868 | /* keep going until we're released */ |
| | 869 | waitLoop: |
| | 870 | for (;;) |
| | 871 | { |
| | 872 | /* |
| | 873 | * Wait for a key, and see what we have. Note that we |
| | 874 | * explicitly do not want to allow any real-time events to |
| | 875 | * occur, so we simply wait forever without timeout. |
| | 876 | */ |
| | 877 | switch(inputKey()) |
| | 878 | { |
| | 879 | case ' ': |
| | 880 | /* space key - end the wait */ |
| | 881 | break waitLoop; |
| | 882 | |
| | 883 | case 's': |
| | 884 | case 'S': |
| | 885 | /* mention that we're saving */ |
| | 886 | gLibMessages.pauseSaving(); |
| | 887 | |
| | 888 | /* |
| | 889 | * set the elapsed time to the time when we started, so |
| | 890 | * that the saved position reflects the time at the |
| | 891 | * start of the pause |
| | 892 | */ |
| | 893 | realTimeManager.setElapsedTime(elapsed); |
| | 894 | |
| | 895 | /* save the game - go run the normal SAVE command */ |
| | 896 | SaveAction.execSystemAction(); |
| | 897 | |
| | 898 | /* show our prompt again */ |
| | 899 | "<.p>"; |
| | 900 | gLibMessages.pausePrompt(); |
| | 901 | |
| | 902 | /* go back to wait for another key */ |
| | 903 | break; |
| | 904 | |
| | 905 | case '[eof]': |
| | 906 | /* end-of-file on keyboard input - throw an error */ |
| | 907 | "\b"; |
| | 908 | throw new EndOfFileException(); |
| | 909 | |
| | 910 | default: |
| | 911 | /* ignore other keys; just go back to wait again */ |
| | 912 | break; |
| | 913 | } |
| | 914 | } |
| | 915 | |
| | 916 | /* show the released-from-pause message */ |
| | 917 | gLibMessages.pauseEnded(); |
| | 918 | |
| | 919 | /* |
| | 920 | * set the real-time clock to the same elapsed game time |
| | 921 | * that we had when we started the pause, so that the |
| | 922 | * elapsed real time of the pause itself doesn't count |
| | 923 | * against the game elapsed time |
| | 924 | */ |
| | 925 | realTimeManager.setElapsedTime(elapsed); |
| | 926 | } |
| | 927 | ; |
| | 928 | |
| | 929 | /* |
| | 930 | * Change to VERBOSE mode. |
| | 931 | */ |
| | 932 | DefineSystemAction(Verbose) |
| | 933 | execSystemAction() |
| | 934 | { |
| | 935 | /* set the global 'verbose' mode */ |
| | 936 | gameMain.verboseMode.isOn = true; |
| | 937 | |
| | 938 | /* acknowledge it */ |
| | 939 | gLibMessages.acknowledgeVerboseMode(true); |
| | 940 | } |
| | 941 | ; |
| | 942 | |
| | 943 | /* |
| | 944 | * Change to TERSE mode. |
| | 945 | */ |
| | 946 | DefineSystemAction(Terse) |
| | 947 | execSystemAction() |
| | 948 | { |
| | 949 | /* set the global 'verbose' mode */ |
| | 950 | gameMain.verboseMode.isOn = nil; |
| | 951 | |
| | 952 | /* acknowledge it */ |
| | 953 | gLibMessages.acknowledgeVerboseMode(nil); |
| | 954 | } |
| | 955 | ; |
| | 956 | |
| | 957 | /* in case the score module isn't present */ |
| | 958 | property showScore; |
| | 959 | property showFullScore; |
| | 960 | property scoreNotify; |
| | 961 | |
| | 962 | /* |
| | 963 | * Show the current score. |
| | 964 | */ |
| | 965 | DefineSystemAction(Score) |
| | 966 | execSystemAction() |
| | 967 | { |
| | 968 | /* show the simple score */ |
| | 969 | if (libGlobal.scoreObj != nil) |
| | 970 | { |
| | 971 | /* show the score */ |
| | 972 | libGlobal.scoreObj.showScore(); |
| | 973 | |
| | 974 | /* |
| | 975 | * Mention the FULL SCORE command to the player if we haven't |
| | 976 | * already. Note that we only want to mention |
| | 977 | */ |
| | 978 | if (!mentionedFullScore) |
| | 979 | { |
| | 980 | /* explain about it */ |
| | 981 | gLibMessages.mentionFullScore; |
| | 982 | |
| | 983 | /* don't mention it again */ |
| | 984 | ScoreAction.mentionedFullScore = true; |
| | 985 | } |
| | 986 | } |
| | 987 | else |
| | 988 | gLibMessages.scoreNotPresent; |
| | 989 | } |
| | 990 | |
| | 991 | /* there's no point in including this in undo */ |
| | 992 | includeInUndo = nil |
| | 993 | |
| | 994 | /* have we mentioned the FULL SCORE command yet? */ |
| | 995 | mentionedFullScore = nil |
| | 996 | ; |
| | 997 | |
| | 998 | /* |
| | 999 | * Show the full score. |
| | 1000 | */ |
| | 1001 | DefineSystemAction(FullScore) |
| | 1002 | execSystemAction() |
| | 1003 | { |
| | 1004 | /* show the full score in response to an explicit player request */ |
| | 1005 | showFullScore(); |
| | 1006 | |
| | 1007 | /* this counts as a mention of the FULL SCORE command */ |
| | 1008 | ScoreAction.mentionedFullScore = true; |
| | 1009 | } |
| | 1010 | |
| | 1011 | /* show the full score */ |
| | 1012 | showFullScore() |
| | 1013 | { |
| | 1014 | /* show the full score */ |
| | 1015 | if (libGlobal.scoreObj != nil) |
| | 1016 | libGlobal.scoreObj.showFullScore(); |
| | 1017 | else |
| | 1018 | gLibMessages.scoreNotPresent; |
| | 1019 | } |
| | 1020 | |
| | 1021 | /* there's no point in including this in undo */ |
| | 1022 | includeInUndo = nil |
| | 1023 | ; |
| | 1024 | |
| | 1025 | /* |
| | 1026 | * Show the NOTIFY status. |
| | 1027 | */ |
| | 1028 | DefineSystemAction(Notify) |
| | 1029 | execSystemAction() |
| | 1030 | { |
| | 1031 | /* show the current notification status */ |
| | 1032 | if (libGlobal.scoreObj != nil) |
| | 1033 | gLibMessages.showNotifyStatus( |
| | 1034 | libGlobal.scoreObj.scoreNotify.isOn); |
| | 1035 | else |
| | 1036 | gLibMessages.commandNotPresent; |
| | 1037 | } |
| | 1038 | ; |
| | 1039 | |
| | 1040 | /* |
| | 1041 | * Turn score change notifications on. |
| | 1042 | */ |
| | 1043 | DefineSystemAction(NotifyOn) |
| | 1044 | execSystemAction() |
| | 1045 | { |
| | 1046 | /* turn notifications on, and acknowledge the status */ |
| | 1047 | if (libGlobal.scoreObj != nil) |
| | 1048 | { |
| | 1049 | libGlobal.scoreObj.scoreNotify.isOn = true; |
| | 1050 | gLibMessages.acknowledgeNotifyStatus(true); |
| | 1051 | } |
| | 1052 | else |
| | 1053 | gLibMessages.commandNotPresent; |
| | 1054 | } |
| | 1055 | ; |
| | 1056 | |
| | 1057 | /* |
| | 1058 | * Turn score change notifications off. |
| | 1059 | */ |
| | 1060 | DefineSystemAction(NotifyOff) |
| | 1061 | execSystemAction() |
| | 1062 | { |
| | 1063 | /* turn notifications off, and acknowledge the status */ |
| | 1064 | if (libGlobal.scoreObj != nil) |
| | 1065 | { |
| | 1066 | libGlobal.scoreObj.scoreNotify.isOn = nil; |
| | 1067 | gLibMessages.acknowledgeNotifyStatus(nil); |
| | 1068 | } |
| | 1069 | else |
| | 1070 | gLibMessages.commandNotPresent; |
| | 1071 | } |
| | 1072 | ; |
| | 1073 | |
| | 1074 | /* |
| | 1075 | * Show version information for the game and the library modules the |
| | 1076 | * game is using. |
| | 1077 | */ |
| | 1078 | DefineSystemAction(Version) |
| | 1079 | execSystemAction() |
| | 1080 | { |
| | 1081 | /* show the version information for each library */ |
| | 1082 | foreach (local cur in ModuleID.getModuleList()) |
| | 1083 | cur.showVersion(); |
| | 1084 | } |
| | 1085 | |
| | 1086 | /* there's no point in including this in undo */ |
| | 1087 | includeInUndo = nil |
| | 1088 | ; |
| | 1089 | |
| | 1090 | /* |
| | 1091 | * Show the credits for the game and the library modules the game |
| | 1092 | * includes. |
| | 1093 | */ |
| | 1094 | DefineSystemAction(Credits) |
| | 1095 | execSystemAction() |
| | 1096 | { |
| | 1097 | /* show the credits for each library */ |
| | 1098 | foreach (local cur in ModuleID.getModuleList()) |
| | 1099 | cur.showCredit(); |
| | 1100 | } |
| | 1101 | |
| | 1102 | /* there's no point in including this in undo */ |
| | 1103 | includeInUndo = nil |
| | 1104 | ; |
| | 1105 | |
| | 1106 | /* |
| | 1107 | * Show the "about" information for the game and library modules. |
| | 1108 | */ |
| | 1109 | DefineSystemAction(About) |
| | 1110 | execSystemAction() |
| | 1111 | { |
| | 1112 | local anyOutput; |
| | 1113 | |
| | 1114 | /* watch for any output while showing module information */ |
| | 1115 | anyOutput = outputManager.curOutputStream |
| | 1116 | .watchForOutput(new function() |
| | 1117 | { |
| | 1118 | /* show information for each module */ |
| | 1119 | foreach (local cur in ModuleID.getModuleList()) |
| | 1120 | cur.showAbout(); |
| | 1121 | }); |
| | 1122 | |
| | 1123 | /* |
| | 1124 | * if we didn't have any ABOUT information to show, display a |
| | 1125 | * message to this effect |
| | 1126 | */ |
| | 1127 | if (!anyOutput) |
| | 1128 | gLibMessages.noAboutInfo; |
| | 1129 | } |
| | 1130 | |
| | 1131 | /* there's no point in including this in undo */ |
| | 1132 | includeInUndo = nil |
| | 1133 | ; |
| | 1134 | |
| | 1135 | /* |
| | 1136 | * A state object that keeps track of our logging (scripting) status. |
| | 1137 | * This is transient, because logging is controlled through the output |
| | 1138 | * layer in the interpreter, which does not participate in any of the |
| | 1139 | * persistence mechanisms. |
| | 1140 | */ |
| | 1141 | transient scriptStatus: object |
| | 1142 | /* |
| | 1143 | * Script file name. This is nil when logging is not in effect, and |
| | 1144 | * is set to the name of the scripting file when a log file is |
| | 1145 | * active. |
| | 1146 | */ |
| | 1147 | scriptFile = nil |
| | 1148 | |
| | 1149 | /* RECORD file name */ |
| | 1150 | recordFile = nil |
| | 1151 | |
| | 1152 | /* have we warned about using NOTE without logging in effect? */ |
| | 1153 | noteWithoutScriptWarning = nil |
| | 1154 | ; |
| | 1155 | |
| | 1156 | /* |
| | 1157 | * A base class for file-oriented actions, such as SCRIPT, RECORD, and |
| | 1158 | * REPLAY. We provide common handling that prompts interactively for a |
| | 1159 | * filename; subclasses must override a few methods and properties to |
| | 1160 | * carry out the specific subclassed operation on the file. |
| | 1161 | */ |
| | 1162 | DefineSystemAction(FileOp) |
| | 1163 | /* our file dialog prompt message */ |
| | 1164 | filePromptMsg = '' |
| | 1165 | |
| | 1166 | /* the file dialog open/save type */ |
| | 1167 | fileDisposition = InFileSave |
| | 1168 | |
| | 1169 | /* the file dialog type ID */ |
| | 1170 | fileTypeID = FileTypeLog |
| | 1171 | |
| | 1172 | /* show our cancellation mesage */ |
| | 1173 | showCancelMsg = "" |
| | 1174 | |
| | 1175 | /* carry out our file operation */ |
| | 1176 | performFileOp(fname, ack) |
| | 1177 | { |
| | 1178 | /* |
| | 1179 | * Each concrete action subclass must override this to carry out |
| | 1180 | * our operation. This is called when the user has successfully |
| | 1181 | * selected a filename for the operatoin. |
| | 1182 | */ |
| | 1183 | } |
| | 1184 | |
| | 1185 | execSystemAction() |
| | 1186 | { |
| | 1187 | /* |
| | 1188 | * ask for a file and carry out our action; since the command is |
| | 1189 | * being performed directly from the command line, we want an |
| | 1190 | * acknowledgment message on success |
| | 1191 | */ |
| | 1192 | setUpFileOp(true); |
| | 1193 | } |
| | 1194 | |
| | 1195 | /* ask for a file, and carry out our operation is we get one */ |
| | 1196 | setUpFileOp(ack) |
| | 1197 | { |
| | 1198 | local result; |
| | 1199 | local origElapsedTime; |
| | 1200 | |
| | 1201 | /* note the current game time */ |
| | 1202 | origElapsedTime = realTimeManager.getElapsedTime(); |
| | 1203 | |
| | 1204 | /* ask for a file */ |
| | 1205 | result = getInputFile(filePromptMsg, fileDisposition, fileTypeID, 0); |
| | 1206 | |
| | 1207 | /* check the inputFile result */ |
| | 1208 | switch(result[1]) |
| | 1209 | { |
| | 1210 | case InFileSuccess: |
| | 1211 | /* carry out our file operation */ |
| | 1212 | performFileOp(result[2], ack); |
| | 1213 | break; |
| | 1214 | |
| | 1215 | case InFileFailure: |
| | 1216 | /* advise of the failure of the prompt */ |
| | 1217 | gLibMessages.filePromptFailed(); |
| | 1218 | break; |
| | 1219 | |
| | 1220 | case InFileCancel: |
| | 1221 | /* acknowledge the cancellation */ |
| | 1222 | showCancelMsg(); |
| | 1223 | break; |
| | 1224 | } |
| | 1225 | |
| | 1226 | /* |
| | 1227 | * restore the original elapsed game time, so that the time spent |
| | 1228 | * in the file selector dialog doesn't count against the game |
| | 1229 | * time |
| | 1230 | */ |
| | 1231 | realTimeManager.setElapsedTime(origElapsedTime); |
| | 1232 | } |
| | 1233 | |
| | 1234 | /* we can't include this in undo, as it affects external files */ |
| | 1235 | includeInUndo = nil |
| | 1236 | ; |
| | 1237 | |
| | 1238 | /* |
| | 1239 | * Turn scripting on. This creates a text file that contains a |
| | 1240 | * transcript of all commands and responses from this point forward. |
| | 1241 | */ |
| | 1242 | DefineAction(Script, FileOpAction) |
| | 1243 | /* our file dialog parameters - ask for a log file to save */ |
| | 1244 | filePromptMsg = (gLibMessages.getScriptingPrompt()) |
| | 1245 | fileTypeID = FileTypeLog |
| | 1246 | fileDisposition = InFileSave |
| | 1247 | |
| | 1248 | /* show our cancellation mesasge */ |
| | 1249 | showCancelMsg() { gLibMessages.scriptingCanceled(); } |
| | 1250 | |
| | 1251 | /* |
| | 1252 | * set up scripting - this can be used to set up scripting |
| | 1253 | * programmatically, in the course of carrying out another action |
| | 1254 | */ |
| | 1255 | setUpScripting(ack) { setUpFileOp(ack); } |
| | 1256 | |
| | 1257 | /* turn on scripting to the given file */ |
| | 1258 | performFileOp(fname, ack) |
| | 1259 | { |
| | 1260 | /* turn on logging */ |
| | 1261 | setLogFile(fname, LogTypeTranscript); |
| | 1262 | |
| | 1263 | /* remember that scripting is in effect */ |
| | 1264 | scriptStatus.scriptFile = fname; |
| | 1265 | |
| | 1266 | /* |
| | 1267 | * forget any past warning that we've issued about NOTE without |
| | 1268 | * a script in effect; the next time scripting isn't active, |
| | 1269 | * we'll want to issue a new warning, since they might not be |
| | 1270 | * aware at that point that the scripting we're starting now has |
| | 1271 | * ended |
| | 1272 | */ |
| | 1273 | scriptStatus.noteWithoutScriptWarning = nil; |
| | 1274 | |
| | 1275 | /* note that logging is active, if acknowledgment is desired */ |
| | 1276 | if (ack) |
| | 1277 | gLibMessages.scriptingOkay(); |
| | 1278 | } |
| | 1279 | ; |
| | 1280 | |
| | 1281 | /* |
| | 1282 | * Subclass of Script action taking a quoted string as part of the |
| | 1283 | * command syntax. The grammar rule must set our fname_ property to a |
| | 1284 | * quotedStringPhrase subproduction. |
| | 1285 | */ |
| | 1286 | DefineAction(ScriptString, ScriptAction) |
| | 1287 | execSystemAction() |
| | 1288 | { |
| | 1289 | /* if there's a filename, we don't need to prompt */ |
| | 1290 | if (fname_ != nil) |
| | 1291 | { |
| | 1292 | /* set up scripting to the filename specified in the command */ |
| | 1293 | performFileOp(fname_.getStringText(), true); |
| | 1294 | } |
| | 1295 | else |
| | 1296 | { |
| | 1297 | /* there's no filename, so prompt as usual */ |
| | 1298 | inherited(); |
| | 1299 | } |
| | 1300 | } |
| | 1301 | ; |
| | 1302 | |
| | 1303 | /* |
| | 1304 | * Turn scripting off. This stops recording the game transcript started |
| | 1305 | * with the most recent SCRIPT command. |
| | 1306 | */ |
| | 1307 | DefineSystemAction(ScriptOff) |
| | 1308 | execSystemAction() |
| | 1309 | { |
| | 1310 | /* turn off scripting */ |
| | 1311 | turnOffScripting(true); |
| | 1312 | } |
| | 1313 | |
| | 1314 | /* turn off scripting */ |
| | 1315 | turnOffScripting(ack) |
| | 1316 | { |
| | 1317 | /* if we're not in a script file, ignore it */ |
| | 1318 | if (scriptStatus.scriptFile == nil) |
| | 1319 | { |
| | 1320 | gLibMessages.scriptOffIgnored(); |
| | 1321 | return; |
| | 1322 | } |
| | 1323 | |
| | 1324 | /* cancel scripting in the interpreter's output layer */ |
| | 1325 | setLogFile(nil, LogTypeTranscript); |
| | 1326 | |
| | 1327 | /* remember that scripting is no longer in effect */ |
| | 1328 | scriptStatus.scriptFile = nil; |
| | 1329 | |
| | 1330 | /* acknowledge the change, if desired */ |
| | 1331 | if (ack) |
| | 1332 | gLibMessages.scriptOffOkay(); |
| | 1333 | } |
| | 1334 | |
| | 1335 | /* we can't include this in undo, as it affects external files */ |
| | 1336 | includeInUndo = nil |
| | 1337 | ; |
| | 1338 | |
| | 1339 | /* |
| | 1340 | * RECORD - this is similar to SCRIPT, but stores a file containing only |
| | 1341 | * the command input, not the output. |
| | 1342 | */ |
| | 1343 | DefineAction(Record, FileOpAction) |
| | 1344 | /* our file dialog parameters - ask for a log file to save */ |
| | 1345 | filePromptMsg = (gLibMessages.getRecordingPrompt()) |
| | 1346 | fileTypeID = FileTypeCmd |
| | 1347 | fileDisposition = InFileSave |
| | 1348 | |
| | 1349 | /* show our cancellation mesasge */ |
| | 1350 | showCancelMsg() { gLibMessages.recordingCanceled(); } |
| | 1351 | |
| | 1352 | /* |
| | 1353 | * set up recording - this can be used to set up scripting |
| | 1354 | * programmatically, in the course of carrying out another action |
| | 1355 | */ |
| | 1356 | setUpRecording(ack) { setUpFileOp(ack); } |
| | 1357 | |
| | 1358 | /* turn on recording to the given file */ |
| | 1359 | performFileOp(fname, ack) |
| | 1360 | { |
| | 1361 | /* turn on command logging */ |
| | 1362 | setLogFile(fname, logFileType); |
| | 1363 | |
| | 1364 | /* remember that recording is in effect */ |
| | 1365 | scriptStatus.recordFile = fname; |
| | 1366 | |
| | 1367 | /* note that logging is active, if acknowledgment is desired */ |
| | 1368 | if (ack) |
| | 1369 | gLibMessages.recordingOkay(); |
| | 1370 | } |
| | 1371 | |
| | 1372 | /* the log file type - by default, we open a regular command log */ |
| | 1373 | logFileType = LogTypeCommand |
| | 1374 | ; |
| | 1375 | |
| | 1376 | /* subclass of Record action that sets up an event script recording */ |
| | 1377 | DefineAction(RecordEvents, RecordAction) |
| | 1378 | logFileType = LogTypeScript |
| | 1379 | ; |
| | 1380 | |
| | 1381 | /* subclass of Record action taking a quoted string for the filename */ |
| | 1382 | DefineAction(RecordString, RecordAction) |
| | 1383 | execSystemAction() |
| | 1384 | { |
| | 1385 | /* set up scripting to the filename specified in the command */ |
| | 1386 | performFileOp(fname_.getStringText(), true); |
| | 1387 | } |
| | 1388 | ; |
| | 1389 | |
| | 1390 | /* subclass of RecordString action that sets up an event script recording */ |
| | 1391 | DefineAction(RecordEventsString, RecordStringAction) |
| | 1392 | logFileType = LogTypeScript |
| | 1393 | ; |
| | 1394 | |
| | 1395 | /* |
| | 1396 | * Turn command recording off. This stops recording the command log |
| | 1397 | * started with the most recent RECORD command. |
| | 1398 | */ |
| | 1399 | DefineSystemAction(RecordOff) |
| | 1400 | execSystemAction() |
| | 1401 | { |
| | 1402 | /* turn off recording */ |
| | 1403 | turnOffRecording(true); |
| | 1404 | } |
| | 1405 | |
| | 1406 | /* turn off recording */ |
| | 1407 | turnOffRecording(ack) |
| | 1408 | { |
| | 1409 | /* if we're not recording anything, ignore it */ |
| | 1410 | if (scriptStatus.recordFile == nil) |
| | 1411 | { |
| | 1412 | gLibMessages.recordOffIgnored(); |
| | 1413 | return; |
| | 1414 | } |
| | 1415 | |
| | 1416 | /* cancel recording in the interpreter's output layer */ |
| | 1417 | setLogFile(nil, LogTypeCommand); |
| | 1418 | |
| | 1419 | /* remember that recording is no longer in effect */ |
| | 1420 | scriptStatus.recordFile = nil; |
| | 1421 | |
| | 1422 | /* acknowledge the change, if desired */ |
| | 1423 | if (ack) |
| | 1424 | gLibMessages.recordOffOkay(); |
| | 1425 | } |
| | 1426 | |
| | 1427 | /* we can't include this in undo, as it affects external files */ |
| | 1428 | includeInUndo = nil |
| | 1429 | ; |
| | 1430 | |
| | 1431 | /* |
| | 1432 | * REPLAY - play back a command log previously recorded. |
| | 1433 | */ |
| | 1434 | DefineAction(Replay, FileOpAction) |
| | 1435 | /* our file dialog parameters - ask for a log file to save */ |
| | 1436 | filePromptMsg = (gLibMessages.getReplayPrompt()) |
| | 1437 | fileTypeID = FileTypeCmd |
| | 1438 | fileDisposition = InFileOpen |
| | 1439 | |
| | 1440 | /* show our cancellation mesasge */ |
| | 1441 | showCancelMsg() { gLibMessages.replayCanceled(); } |
| | 1442 | |
| | 1443 | /* script flags passed to setScriptFile */ |
| | 1444 | scriptOptionFlags = 0 |
| | 1445 | |
| | 1446 | /* replay the given file */ |
| | 1447 | performFileOp(fname, ack) |
| | 1448 | { |
| | 1449 | /* |
| | 1450 | * Note that we're reading from the script file if desired. Do |
| | 1451 | * this before opening the script, so that we display the |
| | 1452 | * acknowledgment even if we're in 'quiet' mode. |
| | 1453 | */ |
| | 1454 | if (ack) |
| | 1455 | gLibMessages.inputScriptOkay(fname); |
| | 1456 | |
| | 1457 | /* activate the script file */ |
| | 1458 | setScriptFile(fname, scriptOptionFlags); |
| | 1459 | } |
| | 1460 | ; |
| | 1461 | |
| | 1462 | /* subclass of Replay action taking a quoted string for the filename */ |
| | 1463 | DefineAction(ReplayString, ReplayAction) |
| | 1464 | execSystemAction() |
| | 1465 | { |
| | 1466 | /* |
| | 1467 | * if there's a string, use the string as the filename; |
| | 1468 | * otherwise, inherit the default handling to ask for a filename |
| | 1469 | */ |
| | 1470 | if (fname_ != nil) |
| | 1471 | { |
| | 1472 | /* set up scripting to the filename specified in the command */ |
| | 1473 | performFileOp(fname_.getStringText(), true); |
| | 1474 | } |
| | 1475 | else |
| | 1476 | { |
| | 1477 | /* inherit the default handling to ask for a filename */ |
| | 1478 | inherited(); |
| | 1479 | } |
| | 1480 | } |
| | 1481 | ; |
| | 1482 | |
| | 1483 | |
| | 1484 | /* in case the footnote module is not present */ |
| | 1485 | property showFootnote; |
| | 1486 | |
| | 1487 | /* |
| | 1488 | * Footnote - this requires a numeric argument parsed via the |
| | 1489 | * numberPhrase production and assigned to the numMatch property. |
| | 1490 | */ |
| | 1491 | DefineSystemAction(Footnote) |
| | 1492 | execSystemAction() |
| | 1493 | { |
| | 1494 | /* ask the Footnote class to do the work */ |
| | 1495 | if (libGlobal.footnoteClass != nil) |
| | 1496 | libGlobal.footnoteClass.showFootnote(numMatch.getval()); |
| | 1497 | else |
| | 1498 | gLibMessages.commandNotPresent; |
| | 1499 | } |
| | 1500 | |
| | 1501 | /* there's no point in including this in undo */ |
| | 1502 | includeInUndo = nil |
| | 1503 | ; |
| | 1504 | |
| | 1505 | property footnoteSettings; |
| | 1506 | |
| | 1507 | /* base class for FOOTNOTES xxx commands */ |
| | 1508 | DefineSystemAction(Footnotes) |
| | 1509 | execSystemAction() |
| | 1510 | { |
| | 1511 | if (libGlobal.footnoteClass != nil) |
| | 1512 | { |
| | 1513 | /* set my footnote status in the global setting */ |
| | 1514 | libGlobal.footnoteClass.footnoteSettings.showFootnotes = |
| | 1515 | showFootnotes; |
| | 1516 | |
| | 1517 | /* acknowledge it */ |
| | 1518 | gLibMessages.acknowledgeFootnoteStatus(showFootnotes); |
| | 1519 | } |
| | 1520 | else |
| | 1521 | gLibMessages.commandNotPresent; |
| | 1522 | } |
| | 1523 | |
| | 1524 | /* |
| | 1525 | * the footnote status I set when this command is activated - this |
| | 1526 | * must be overridden by each subclass |
| | 1527 | */ |
| | 1528 | showFootnotes = nil |
| | 1529 | ; |
| | 1530 | |
| | 1531 | DefineAction(FootnotesFull, FootnotesAction) |
| | 1532 | showFootnotes = FootnotesFull |
| | 1533 | ; |
| | 1534 | |
| | 1535 | DefineAction(FootnotesMedium, FootnotesAction) |
| | 1536 | showFootnotes = FootnotesMedium |
| | 1537 | ; |
| | 1538 | |
| | 1539 | DefineAction(FootnotesOff, FootnotesAction) |
| | 1540 | showFootnotes = FootnotesOff |
| | 1541 | ; |
| | 1542 | |
| | 1543 | DefineSystemAction(FootnotesStatus) |
| | 1544 | execSystemAction() |
| | 1545 | { |
| | 1546 | /* show the current status */ |
| | 1547 | if (libGlobal.footnoteClass != nil) |
| | 1548 | gLibMessages.showFootnoteStatus(libGlobal.footnoteClass. |
| | 1549 | footnoteSettings.showFootnotes); |
| | 1550 | else |
| | 1551 | gLibMessages.commandNotPresent; |
| | 1552 | } |
| | 1553 | |
| | 1554 | /* there's no point in including this in undo */ |
| | 1555 | includeInUndo = nil |
| | 1556 | ; |
| | 1557 | |
| | 1558 | DefineIAction(Inventory) |
| | 1559 | execAction() |
| | 1560 | { |
| | 1561 | /* show the actor's inventory in the current mode */ |
| | 1562 | gActor.showInventory(inventoryMode == InventoryTall); |
| | 1563 | } |
| | 1564 | |
| | 1565 | /* current inventory mode - start in 'wide' mode by default */ |
| | 1566 | inventoryMode = InventoryWide; |
| | 1567 | ; |
| | 1568 | |
| | 1569 | DefineIAction(InventoryTall) |
| | 1570 | execAction() |
| | 1571 | { |
| | 1572 | /* set inventory mode to 'tall' */ |
| | 1573 | InventoryAction.inventoryMode = InventoryTall; |
| | 1574 | |
| | 1575 | /* run the inventory action */ |
| | 1576 | InventoryAction.checkAction(); |
| | 1577 | InventoryAction.execAction(); |
| | 1578 | } |
| | 1579 | ; |
| | 1580 | |
| | 1581 | DefineIAction(InventoryWide) |
| | 1582 | execAction() |
| | 1583 | { |
| | 1584 | /* set inventory mode to 'wide' */ |
| | 1585 | InventoryAction.inventoryMode = InventoryWide; |
| | 1586 | |
| | 1587 | /* run the inventory action */ |
| | 1588 | InventoryAction.checkAction(); |
| | 1589 | InventoryAction.execAction(); |
| | 1590 | } |
| | 1591 | ; |
| | 1592 | |
| | 1593 | DefineIAction(Wait) |
| | 1594 | execAction() |
| | 1595 | { |
| | 1596 | /* just show the "time passes" message */ |
| | 1597 | defaultReport(&timePassesMsg); |
| | 1598 | } |
| | 1599 | ; |
| | 1600 | |
| | 1601 | DefineIAction(Look) |
| | 1602 | execAction() |
| | 1603 | { |
| | 1604 | /* show the actor's current location in verbose mode */ |
| | 1605 | gActor.lookAround(true); |
| | 1606 | } |
| | 1607 | ; |
| | 1608 | |
| | 1609 | DefineIAction(Sleep) |
| | 1610 | execAction() |
| | 1611 | { |
| | 1612 | /* let the actor handle it */ |
| | 1613 | gActor.goToSleep(); |
| | 1614 | } |
| | 1615 | ; |
| | 1616 | |
| | 1617 | DefineTAction(Take) |
| | 1618 | /* this is a basic inventory-management verb, so allow ALL with it */ |
| | 1619 | actionAllowsAll = true |
| | 1620 | |
| | 1621 | /* get the ALL list for the direct object */ |
| | 1622 | getAllDobj(actor, scopeList) |
| | 1623 | { |
| | 1624 | local locList; |
| | 1625 | local dropLoc; |
| | 1626 | local actorLoc; |
| | 1627 | |
| | 1628 | /* |
| | 1629 | * Include all of the objects that are directly in the actor's |
| | 1630 | * immediate container, the container's container, and so on out |
| | 1631 | * to the "drop destination" location (which is where things go |
| | 1632 | * when we DROP them, and is meant to represent the nearest |
| | 1633 | * platform-like or floor-like container). Also include anything |
| | 1634 | * that's directly in anything fixed in place within one of these |
| | 1635 | * containers. Don't include anything that actually contains the |
| | 1636 | * actor, since we normally can't pick up something we're inside. |
| | 1637 | * |
| | 1638 | * Start by getting the actor's immediate location and drop |
| | 1639 | * destination location. |
| | 1640 | */ |
| | 1641 | actorLoc = actor.location; |
| | 1642 | dropLoc = actor.getDropDestination(nil, nil); |
| | 1643 | |
| | 1644 | /* |
| | 1645 | * create a vector to hold the location list, and start it off |
| | 1646 | * with the drop location |
| | 1647 | */ |
| | 1648 | locList = new Vector(10); |
| | 1649 | locList.append(dropLoc); |
| | 1650 | |
| | 1651 | /* now work up the location list until we hit the drop location */ |
| | 1652 | for (local cur = actorLoc ; cur != nil && cur != dropLoc ; |
| | 1653 | cur = cur.location) |
| | 1654 | { |
| | 1655 | /* add this container to the list */ |
| | 1656 | locList.append(cur); |
| | 1657 | } |
| | 1658 | |
| | 1659 | /* |
| | 1660 | * now generate the subset of in-scope objects that are directly |
| | 1661 | * in any of these locations (or directly in items fixed in place |
| | 1662 | * within any of these locations), and return the result |
| | 1663 | */ |
| | 1664 | return scopeList.subset( |
| | 1665 | {x: (locList.indexWhich( |
| | 1666 | {loc: x.isDirectlyIn(loc) || x.isInFixedIn(loc)}) != nil |
| | 1667 | && !actor.isIn(x)) }); |
| | 1668 | } |
| | 1669 | ; |
| | 1670 | |
| | 1671 | DefineTIAction(TakeFrom) |
| | 1672 | /* this is a basic inventory-management verb, so allow ALL with it */ |
| | 1673 | actionAllowsAll = true |
| | 1674 | |
| | 1675 | /* get the ALL list for the direct object */ |
| | 1676 | getAllDobj(actor, scopeList) |
| | 1677 | { |
| | 1678 | /* ask the indirect object for the list */ |
| | 1679 | return getIobj() == nil |
| | 1680 | ? [] |
| | 1681 | : getIobj().getAllForTakeFrom(scopeList); |
| | 1682 | } |
| | 1683 | ; |
| | 1684 | |
| | 1685 | DefineTAction(Remove) |
| | 1686 | ; |
| | 1687 | |
| | 1688 | DefineTAction(Drop) |
| | 1689 | /* this is a basic inventory-management verb, so allow ALL with it */ |
| | 1690 | actionAllowsAll = true |
| | 1691 | |
| | 1692 | /* get the ALL list for the direct object */ |
| | 1693 | getAllDobj(actor, scopeList) |
| | 1694 | { |
| | 1695 | /* include only objects directly held by the actor */ |
| | 1696 | return scopeList.subset({x: x.isDirectlyIn(actor)}); |
| | 1697 | } |
| | 1698 | ; |
| | 1699 | |
| | 1700 | DefineTAction(Examine) |
| | 1701 | ; |
| | 1702 | |
| | 1703 | DefineTAction(Read) |
| | 1704 | ; |
| | 1705 | |
| | 1706 | DefineTAction(LookIn) |
| | 1707 | ; |
| | 1708 | |
| | 1709 | DefineTAction(Search) |
| | 1710 | ; |
| | 1711 | |
| | 1712 | DefineTAction(LookUnder) |
| | 1713 | ; |
| | 1714 | |
| | 1715 | DefineTAction(LookBehind) |
| | 1716 | ; |
| | 1717 | |
| | 1718 | DefineTAction(LookThrough) |
| | 1719 | ; |
| | 1720 | |
| | 1721 | DefineTAction(Feel) |
| | 1722 | ; |
| | 1723 | |
| | 1724 | DefineTAction(Taste) |
| | 1725 | ; |
| | 1726 | |
| | 1727 | DefineTAction(Smell) |
| | 1728 | ; |
| | 1729 | |
| | 1730 | DefineTAction(ListenTo) |
| | 1731 | ; |
| | 1732 | |
| | 1733 | /* |
| | 1734 | * Base class for undirected sensing, such as "listen" or "smell" with no |
| | 1735 | * object. We'll scan for things that have a presence in the |
| | 1736 | * corresponding sense and describe each one. |
| | 1737 | */ |
| | 1738 | DefineIAction(SenseImplicit) |
| | 1739 | /* the sense in which I operate */ |
| | 1740 | mySense = nil |
| | 1741 | |
| | 1742 | /* the object property to display this sense's description */ |
| | 1743 | descProp = nil |
| | 1744 | |
| | 1745 | /* the default message to display if we find nothing specific to sense */ |
| | 1746 | defaultMsgProp = nil |
| | 1747 | |
| | 1748 | /* the Lister we use to show the items */ |
| | 1749 | resultLister = nil |
| | 1750 | |
| | 1751 | /* execute the action */ |
| | 1752 | execAction() |
| | 1753 | { |
| | 1754 | local senseTab; |
| | 1755 | local presenceList; |
| | 1756 | |
| | 1757 | /* get a list of everything in range of this sense for the actor */ |
| | 1758 | senseTab = gActor.senseInfoTable(mySense); |
| | 1759 | |
| | 1760 | /* get a list of everything with a presence in this sense */ |
| | 1761 | presenceList = senseInfoTableSubset(senseTab, |
| | 1762 | {obj, info: obj.(mySense.presenceProp)}); |
| | 1763 | |
| | 1764 | /* |
| | 1765 | * if there's anything in the list, show it; otherwise, show a |
| | 1766 | * default report |
| | 1767 | */ |
| | 1768 | if (presenceList.length() != 0) |
| | 1769 | { |
| | 1770 | /* show the list using our lister */ |
| | 1771 | resultLister.showList(gActor, nil, presenceList, 0, 0, |
| | 1772 | senseTab, nil); |
| | 1773 | } |
| | 1774 | else |
| | 1775 | { |
| | 1776 | /* there's nothing to show - say so */ |
| | 1777 | defaultReport(defaultMsgProp); |
| | 1778 | } |
| | 1779 | } |
| | 1780 | ; |
| | 1781 | |
| | 1782 | DefineAction(SmellImplicit, SenseImplicitAction) |
| | 1783 | mySense = smell |
| | 1784 | descProp = &smellDesc |
| | 1785 | defaultMsgProp = ¬hingToSmellMsg |
| | 1786 | resultLister = smellActionLister |
| | 1787 | ; |
| | 1788 | |
| | 1789 | DefineAction(ListenImplicit, SenseImplicitAction) |
| | 1790 | mySense = sound |
| | 1791 | descProp = &soundDesc |
| | 1792 | defaultMsgProp = ¬hingToHearMsg |
| | 1793 | resultLister = listenActionLister |
| | 1794 | ; |
| | 1795 | |
| | 1796 | DefineTIAction(PutIn) |
| | 1797 | /* this is a basic inventory-management verb, so allow ALL with it */ |
| | 1798 | actionAllowsAll = true |
| | 1799 | |
| | 1800 | /* get the ALL list for the direct object */ |
| | 1801 | getAllDobj(actor, scopeList) |
| | 1802 | { |
| | 1803 | local loc; |
| | 1804 | local iobj = nil; |
| | 1805 | local iobjIdent = nil; |
| | 1806 | |
| | 1807 | /* get the actor's location */ |
| | 1808 | loc = actor.location; |
| | 1809 | |
| | 1810 | /* if we have an iobj list, retrieve its first element */ |
| | 1811 | if (iobjList_ != nil && iobjList_.length() > 0) |
| | 1812 | { |
| | 1813 | iobj = iobjList_[1].obj_; |
| | 1814 | iobjIdent = iobj.getIdentityObject(); |
| | 1815 | } |
| | 1816 | |
| | 1817 | /* |
| | 1818 | * Include objects that are directly in the actor's location, or |
| | 1819 | * within fixed items in the actor's location, or directly in the |
| | 1820 | * actor's inventory. |
| | 1821 | * |
| | 1822 | * Exclude the indirect object and its "identity" object (since |
| | 1823 | * we obviously can't put the indirect object in itself), and |
| | 1824 | * exclude everything already directly in the indirect object. |
| | 1825 | */ |
| | 1826 | return scopeList.subset({x: |
| | 1827 | (x.isDirectlyIn(loc) |
| | 1828 | || x.isInFixedIn(loc) |
| | 1829 | || x.isDirectlyIn(actor)) |
| | 1830 | && x != iobj |
| | 1831 | && x != iobjIdent |
| | 1832 | && !x.isDirectlyIn(iobj)}); |
| | 1833 | } |
| | 1834 | ; |
| | 1835 | |
| | 1836 | DefineTIAction(PutOn) |
| | 1837 | /* this is a basic inventory-management verb, so allow ALL with it */ |
| | 1838 | actionAllowsAll = true |
| | 1839 | |
| | 1840 | /* get the ALL list for the direct object */ |
| | 1841 | getAllDobj(actor, scopeList) |
| | 1842 | { |
| | 1843 | /* use the same strategy that we do in PutIn */ |
| | 1844 | local loc = actor.location; |
| | 1845 | return scopeList.subset({x: |
| | 1846 | (x.isDirectlyIn(loc) |
| | 1847 | || x.isInFixedIn(loc) |
| | 1848 | || x.isDirectlyIn(actor)) |
| | 1849 | && x != getIobj() |
| | 1850 | && !x.isDirectlyIn(getIobj())}); |
| | 1851 | } |
| | 1852 | ; |
| | 1853 | |
| | 1854 | DefineTIAction(PutUnder) |
| | 1855 | ; |
| | 1856 | |
| | 1857 | DefineTIAction(PutBehind) |
| | 1858 | ; |
| | 1859 | |
| | 1860 | DefineTAction(Wear) |
| | 1861 | ; |
| | 1862 | |
| | 1863 | DefineTAction(Doff) |
| | 1864 | ; |
| | 1865 | |
| | 1866 | DefineConvTopicTAction(AskFor, IndirectObject) |
| | 1867 | ; |
| | 1868 | |
| | 1869 | DefineConvTopicTAction(AskAbout, IndirectObject) |
| | 1870 | ; |
| | 1871 | |
| | 1872 | DefineConvTopicTAction(TellAbout, IndirectObject) |
| | 1873 | /* |
| | 1874 | * TELL ABOUT is a conversational address, as opposed to an order, |
| | 1875 | * if the direct object of the action is the same as the issuer: in |
| | 1876 | * this case, the command has the form <actor>, TELL ME ABOUT |
| | 1877 | * <topic>, which has exactly the same meaning as ASK <actor> ABOUT |
| | 1878 | * <topic>. |
| | 1879 | */ |
| | 1880 | isConversational(issuer) |
| | 1881 | { |
| | 1882 | local dobj; |
| | 1883 | |
| | 1884 | /* |
| | 1885 | * if the resolved direct object matches the issuer, it's |
| | 1886 | * conversational |
| | 1887 | */ |
| | 1888 | dobj = getResolvedDobjList(); |
| | 1889 | return (dobj.length() == 1 && dobj[1] == issuer); |
| | 1890 | } |
| | 1891 | ; |
| | 1892 | |
| | 1893 | /* |
| | 1894 | * AskVague and TellVague are for syntactically incorrect phrasings that |
| | 1895 | * a player might accidentally type, especially in conjunction with a |
| | 1896 | * past SpecialTopic prompt; in English, for example, we define these as |
| | 1897 | * ASK <actor> <topic> and TELL <actor> <topic>. These are used only to |
| | 1898 | * provide more helpful error messages. |
| | 1899 | */ |
| | 1900 | DefineTopicTAction(AskVague, IndirectObject) |
| | 1901 | ; |
| | 1902 | DefineTopicTAction(TellVague, IndirectObject) |
| | 1903 | ; |
| | 1904 | |
| | 1905 | DefineConvIAction(Hello) |
| | 1906 | execAction() |
| | 1907 | { |
| | 1908 | /* the issuing actor is saying hello to the target actor */ |
| | 1909 | gIssuingActor.sayHello(gActor); |
| | 1910 | } |
| | 1911 | ; |
| | 1912 | |
| | 1913 | DefineConvIAction(Goodbye) |
| | 1914 | execAction() |
| | 1915 | { |
| | 1916 | /* the issuing actor is saying goodbye to the target actor */ |
| | 1917 | gIssuingActor.sayGoodbye(gActor); |
| | 1918 | } |
| | 1919 | ; |
| | 1920 | |
| | 1921 | DefineConvIAction(Yes) |
| | 1922 | execAction() |
| | 1923 | { |
| | 1924 | /* the issuing actor is saying yes to the target actor */ |
| | 1925 | gIssuingActor.sayYes(gActor); |
| | 1926 | } |
| | 1927 | ; |
| | 1928 | |
| | 1929 | DefineConvIAction(No) |
| | 1930 | execAction() |
| | 1931 | { |
| | 1932 | /* the issuing actor is saying no to the target actor */ |
| | 1933 | gIssuingActor.sayNo(gActor); |
| | 1934 | } |
| | 1935 | ; |
| | 1936 | |
| | 1937 | /* |
| | 1938 | * Invoke the active SpecialTopic. This isn't a real command - the |
| | 1939 | * player will never actually type this; rather, it's a pseudo-command |
| | 1940 | * that we send to ourselves from a string pre-parser when we recognize |
| | 1941 | * input that matches a SpecialTopic's custom command syntax. |
| | 1942 | * |
| | 1943 | * Note that we actually define the syntax for this command right here |
| | 1944 | * in the language-independent library, because this isn't a real |
| | 1945 | * command. The user never needs to type this command, since it's |
| | 1946 | * something we generate internally. The only important language issue |
| | 1947 | * is that we use a command keyword that no language will ever want to |
| | 1948 | * use for a real command, so we intentionally use some near-English |
| | 1949 | * gibberish. |
| | 1950 | */ |
| | 1951 | DefineLiteralAction(SpecialTopic) |
| | 1952 | execAction() |
| | 1953 | { |
| | 1954 | /* |
| | 1955 | * the issuing actor is saying the current special topic to the |
| | 1956 | * actor's current interlocutor |
| | 1957 | */ |
| | 1958 | gIssuingActor.saySpecialTopic(); |
| | 1959 | } |
| | 1960 | |
| | 1961 | /* |
| | 1962 | * Repeat the action, for an AGAIN command. We need to make sure |
| | 1963 | * the special text interpretation we gave to the command still |
| | 1964 | * holds; if not, reparse the original text and try that. |
| | 1965 | */ |
| | 1966 | repeatAction(lastTargetActor, lastTargetActorPhrase, |
| | 1967 | lastIssuingActor, countsAsIssuerTurn) |
| | 1968 | { |
| | 1969 | local cmd; |
| | 1970 | |
| | 1971 | /* get the original text the player entered */ |
| | 1972 | cmd = getEnteredText(); |
| | 1973 | |
| | 1974 | /* |
| | 1975 | * try running this through the special topic pre-parser again, |
| | 1976 | * to see if it still has the special meaning |
| | 1977 | */ |
| | 1978 | if (specialTopicPreParser.doParsing(cmd, rmcCommand) |
| | 1979 | .startsWith('xspcltopic ')) |
| | 1980 | { |
| | 1981 | /* |
| | 1982 | * it still has the special meaning, so simply execute as we |
| | 1983 | * normally would, by inheriting the standard Action |
| | 1984 | * handling |
| | 1985 | */ |
| | 1986 | inherited(lastTargetActor, lastTargetActorPhrase, |
| | 1987 | lastIssuingActor, countsAsIssuerTurn); |
| | 1988 | } |
| | 1989 | else |
| | 1990 | { |
| | 1991 | /* |
| | 1992 | * The command no longer has the special meaning it did on |
| | 1993 | * the last command, so we can't repeat this command. |
| | 1994 | */ |
| | 1995 | gLibMessages.againNotPossible(lastIssuingActor); |
| | 1996 | } |
| | 1997 | } |
| | 1998 | |
| | 1999 | /* |
| | 2000 | * Get the original player-entered text. This is our literal |
| | 2001 | * phrase, with the embedded-quote encoding decoded. |
| | 2002 | */ |
| | 2003 | getEnteredText() { return decodeOrig(getLiteral()); } |
| | 2004 | |
| | 2005 | /* |
| | 2006 | * encode the original text for our literal phrase: turn double |
| | 2007 | * quotes into '%q' sequences, and turn percent signs into '%%' |
| | 2008 | * sequences |
| | 2009 | */ |
| | 2010 | encodeOrig(txt) |
| | 2011 | { |
| | 2012 | /* first, replace any percent signs with '%%' sequences */ |
| | 2013 | txt = txt.findReplace('%', '%%', ReplaceAll); |
| | 2014 | |
| | 2015 | /* next, replace any double quotes with '%q' sequences */ |
| | 2016 | txt = txt.findReplace('"', '%q', ReplaceAll); |
| | 2017 | |
| | 2018 | /* return the text */ |
| | 2019 | return txt; |
| | 2020 | } |
| | 2021 | |
| | 2022 | /* decode our encoding */ |
| | 2023 | decodeOrig(txt) |
| | 2024 | { |
| | 2025 | /* find each '%' sequence and decode it */ |
| | 2026 | for (local idx = 1 ; idx <= txt.length() ; ) |
| | 2027 | { |
| | 2028 | /* find the next '%' */ |
| | 2029 | idx = txt.find('%', idx); |
| | 2030 | |
| | 2031 | /* if we found nothing, we're done */ |
| | 2032 | if (idx == nil) |
| | 2033 | break; |
| | 2034 | |
| | 2035 | /* |
| | 2036 | * if it's the last character, ignore it, as all of our |
| | 2037 | * encodings are two characters long |
| | 2038 | */ |
| | 2039 | if (idx == txt.length()) |
| | 2040 | break; |
| | 2041 | |
| | 2042 | /* check what we have next */ |
| | 2043 | switch (txt.substr(idx + 1, 1)) |
| | 2044 | { |
| | 2045 | case 'q': |
| | 2046 | /* it's a quote */ |
| | 2047 | txt = txt.findReplace('%q', '"', ReplaceOnce, idx); |
| | 2048 | break; |
| | 2049 | |
| | 2050 | case '%': |
| | 2051 | /* it's a single percent sign */ |
| | 2052 | txt = txt.findReplace('%%', '%', ReplaceOnce, idx); |
| | 2053 | break; |
| | 2054 | } |
| | 2055 | |
| | 2056 | /* continue searching from the next character */ |
| | 2057 | ++idx; |
| | 2058 | } |
| | 2059 | |
| | 2060 | /* return the result */ |
| | 2061 | return txt; |
| | 2062 | } |
| | 2063 | ; |
| | 2064 | |
| | 2065 | grammar predicate(SpecialTopic): |
| | 2066 | 'xspcltopic' literalPhrase->literalMatch |
| | 2067 | : SpecialTopicAction |
| | 2068 | |
| | 2069 | /* |
| | 2070 | * Use the text of the command as originally typed by the player as |
| | 2071 | * our apparent original text. |
| | 2072 | */ |
| | 2073 | getOrigText() { return getEnteredText(); } |
| | 2074 | ; |
| | 2075 | |
| | 2076 | /* in case they try typing just 'xspcltopic' */ |
| | 2077 | grammar predicate(EmptySpecialTopic): |
| | 2078 | 'xspcltopic' : IAction |
| | 2079 | |
| | 2080 | /* just act like we don't understand this command */ |
| | 2081 | execAction() { gLibMessages.commandNotPresent; } |
| | 2082 | ; |
| | 2083 | |
| | 2084 | DefineTAction(Kiss) |
| | 2085 | ; |
| | 2086 | |
| | 2087 | DefineIAction(Yell) |
| | 2088 | execAction() |
| | 2089 | { |
| | 2090 | /* yelling generally has no effect; issue a default response */ |
| | 2091 | mainReport(&okayYellMsg); |
| | 2092 | } |
| | 2093 | ; |
| | 2094 | |
| | 2095 | DefineTAction(TalkTo) |
| | 2096 | ; |
| | 2097 | |
| | 2098 | DefineSystemAction(Topics) |
| | 2099 | execSystemAction() |
| | 2100 | { |
| | 2101 | /* check to see if any suggestions are defined in the entire game */ |
| | 2102 | if (firstObj(SuggestedTopic, ObjInstances) != nil) |
| | 2103 | { |
| | 2104 | /* we have topics - let the actor handle it */ |
| | 2105 | gActor.suggestTopics(true); |
| | 2106 | } |
| | 2107 | else |
| | 2108 | { |
| | 2109 | /* there are no topics at all, so this command isn't used */ |
| | 2110 | gLibMessages.commandNotPresent; |
| | 2111 | } |
| | 2112 | } |
| | 2113 | |
| | 2114 | /* don't include this in undo */ |
| | 2115 | includeInUndo = nil |
| | 2116 | ; |
| | 2117 | |
| | 2118 | DefineTIAction(GiveTo) |
| | 2119 | getDefaultIobj(np, resolver) |
| | 2120 | { |
| | 2121 | local obj; |
| | 2122 | |
| | 2123 | /* check the actor for a current interlocutor */ |
| | 2124 | obj = resolver.getTargetActor().getCurrentInterlocutor(); |
| | 2125 | if (obj != nil) |
| | 2126 | return [new ResolveInfo(obj, 0)]; |
| | 2127 | else |
| | 2128 | return inherited(np, resolver); |
| | 2129 | } |
| | 2130 | ; |
| | 2131 | |
| | 2132 | /* |
| | 2133 | * Define a global remapping to transform commands of the form "X, GIVE |
| | 2134 | * ME Y" to the format "ME, ASK X FOR Y". This makes it easier to write |
| | 2135 | * the code to handle these sorts of exchanges, since it means you only |
| | 2136 | * have to write it in the ASK FOR handler. |
| | 2137 | */ |
| | 2138 | giveMeToAskFor: GlobalRemapping |
| | 2139 | /* |
| | 2140 | * Remap a command, if applicable. We look for commands of the form |
| | 2141 | * "X, GIVE ME Y": we look for a GiveTo action whose indirect object |
| | 2142 | * is the same as the issuing actor. When we find this form of |
| | 2143 | * command, we rewrite it to "ME, ASK X FOR Y". |
| | 2144 | */ |
| | 2145 | getRemapping(issuingActor, targetActor, action) |
| | 2146 | { |
| | 2147 | /* |
| | 2148 | * if it's of the form "X, GIVE Y TO Z", where Z is the issuing |
| | 2149 | * actor (generally ME, but it could conceivably be someone |
| | 2150 | * else), transform it into "Z, ASK X FOR Y". |
| | 2151 | */ |
| | 2152 | if (action.ofKind(GiveToAction) |
| | 2153 | && action.canIobjResolveTo(issuingActor)) |
| | 2154 | { |
| | 2155 | /* create the ASK FOR action */ |
| | 2156 | local newAction = AskForAction.createActionInstance(); |
| | 2157 | |
| | 2158 | /* remember the original version of the action */ |
| | 2159 | newAction.setOriginalAction(action); |
| | 2160 | |
| | 2161 | /* |
| | 2162 | * Changing the phrasing from "X, GIVE Y TO Z" to "Z, ASK X |
| | 2163 | * FOR Y" will change the target actor from X in the old |
| | 2164 | * version to Z in the new version. In the original format, |
| | 2165 | * the pronouns "you", "your", and "yours" implicitly refers |
| | 2166 | * to Z ("Bob, give me your book" implies "bob's book"). The |
| | 2167 | * rewrite will change that, though - assuming that Z is a |
| | 2168 | * second-person actor, "you" will by default refer to Z in |
| | 2169 | * the rewrite. In order to preserve the original meaning, |
| | 2170 | * we have to override "you" in the rewrite so that it |
| | 2171 | * continues to refer to "X", which we can do using a pronoun |
| | 2172 | * override in the new action. |
| | 2173 | */ |
| | 2174 | newAction.setPronounOverride(PronounYou, targetActor); |
| | 2175 | |
| | 2176 | /* |
| | 2177 | * The direct object - the person we're asking - is the |
| | 2178 | * original target actor ("bob" in "bob, give me x"). Since |
| | 2179 | * this is a specific object, we need to wrap it in a |
| | 2180 | * PreResolvedProd. |
| | 2181 | */ |
| | 2182 | local dobj = new PreResolvedProd(targetActor); |
| | 2183 | |
| | 2184 | /* |
| | 2185 | * The thing we're asking for is the original direct object. |
| | 2186 | * ASK FOR takes a topic phrase for its indirect object, |
| | 2187 | * whereas GIVE TO takes a regular noun phrase. The two |
| | 2188 | * aren't quite identical syntactically, so we'll do better |
| | 2189 | * if we re-parse the original dobj noun phrase as a topic |
| | 2190 | * phrase. Fortunately, this is easy... |
| | 2191 | */ |
| | 2192 | local iobj = newAction.reparseMatchAsTopic( |
| | 2193 | action.dobjMatch, issuingActor, issuingActor); |
| | 2194 | |
| | 2195 | /* set the object match trees */ |
| | 2196 | newAction.setObjectMatches(dobj, iobj); |
| | 2197 | |
| | 2198 | /* |
| | 2199 | * Return the new command, addressing the *issuing* actor |
| | 2200 | * this time around. |
| | 2201 | */ |
| | 2202 | return [issuingActor, newAction]; |
| | 2203 | } |
| | 2204 | |
| | 2205 | /* it's not ours */ |
| | 2206 | return nil; |
| | 2207 | } |
| | 2208 | ; |
| | 2209 | |
| | 2210 | |
| | 2211 | DefineTIAction(ShowTo) |
| | 2212 | getDefaultIobj(np, resolver) |
| | 2213 | { |
| | 2214 | local obj; |
| | 2215 | |
| | 2216 | /* check the actor for a current interlocutor */ |
| | 2217 | obj = resolver.getTargetActor().getCurrentInterlocutor(); |
| | 2218 | if (obj != nil) |
| | 2219 | return [new ResolveInfo(obj, 0)]; |
| | 2220 | else |
| | 2221 | return inherited(np, resolver); |
| | 2222 | } |
| | 2223 | ; |
| | 2224 | |
| | 2225 | DefineTAction(Follow) |
| | 2226 | /* |
| | 2227 | * For resolving our direct object, we want to include in the scope |
| | 2228 | * any item that isn't present but which the actor saw departing the |
| | 2229 | * present location. |
| | 2230 | */ |
| | 2231 | initResolver(issuingActor, targetActor) |
| | 2232 | { |
| | 2233 | /* inherit the base resolver initialization */ |
| | 2234 | inherited(issuingActor, targetActor); |
| | 2235 | |
| | 2236 | /* |
| | 2237 | * add to the scope all of the actor's followable objects - |
| | 2238 | * these are the objects which the actor has witnessed leaving |
| | 2239 | * the actor's present location |
| | 2240 | */ |
| | 2241 | scope_ = scope_.appendUnique(targetActor.getFollowables()); |
| | 2242 | } |
| | 2243 | ; |
| | 2244 | |
| | 2245 | DefineTAction(Attack) |
| | 2246 | ; |
| | 2247 | |
| | 2248 | DefineTIAction(AttackWith) |
| | 2249 | /* |
| | 2250 | * for the indirect object, limit 'all' and defaults to the items in |
| | 2251 | * inventory |
| | 2252 | */ |
| | 2253 | getAllIobj(actor, scopeList) |
| | 2254 | { |
| | 2255 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2256 | } |
| | 2257 | ; |
| | 2258 | |
| | 2259 | DefineTAction(Throw) |
| | 2260 | ; |
| | 2261 | |
| | 2262 | DefineTAction(ThrowDir) |
| | 2263 | /* get the direction of the throwing (as a Direction object) */ |
| | 2264 | getDirection() { return dirMatch.dir; } |
| | 2265 | ; |
| | 2266 | |
| | 2267 | DefineTIAction(ThrowAt) |
| | 2268 | ; |
| | 2269 | |
| | 2270 | DefineTIAction(ThrowTo) |
| | 2271 | ; |
| | 2272 | |
| | 2273 | DefineTAction(Dig) |
| | 2274 | ; |
| | 2275 | |
| | 2276 | DefineTIAction(DigWith) |
| | 2277 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2278 | getAllIobj(actor, scopeList) |
| | 2279 | { |
| | 2280 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2281 | } |
| | 2282 | ; |
| | 2283 | |
| | 2284 | DefineIAction(Jump) |
| | 2285 | preCond = [actorStanding] |
| | 2286 | execAction() |
| | 2287 | { |
| | 2288 | /* show the default report for jumping in place */ |
| | 2289 | mainReport(&okayJumpMsg); |
| | 2290 | } |
| | 2291 | ; |
| | 2292 | |
| | 2293 | DefineTAction(JumpOver) |
| | 2294 | ; |
| | 2295 | |
| | 2296 | DefineTAction(JumpOff) |
| | 2297 | ; |
| | 2298 | |
| | 2299 | DefineIAction(JumpOffI) |
| | 2300 | execAction() |
| | 2301 | { |
| | 2302 | mainReport(&cannotJumpOffHereMsg); |
| | 2303 | } |
| | 2304 | ; |
| | 2305 | |
| | 2306 | DefineTAction(Push) |
| | 2307 | ; |
| | 2308 | |
| | 2309 | DefineTAction(Pull) |
| | 2310 | ; |
| | 2311 | |
| | 2312 | DefineTAction(Move) |
| | 2313 | ; |
| | 2314 | |
| | 2315 | DefineTIAction(MoveWith) |
| | 2316 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2317 | getAllIobj(actor, scopeList) |
| | 2318 | { |
| | 2319 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2320 | } |
| | 2321 | ; |
| | 2322 | |
| | 2323 | DefineTIAction(MoveTo) |
| | 2324 | ; |
| | 2325 | |
| | 2326 | DefineTAction(Turn) |
| | 2327 | ; |
| | 2328 | |
| | 2329 | DefineTIAction(TurnWith) |
| | 2330 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2331 | getAllIobj(actor, scopeList) |
| | 2332 | { |
| | 2333 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2334 | } |
| | 2335 | ; |
| | 2336 | |
| | 2337 | DefineLiteralTAction(TurnTo, IndirectObject) |
| | 2338 | ; |
| | 2339 | |
| | 2340 | DefineTAction(Set) |
| | 2341 | ; |
| | 2342 | |
| | 2343 | DefineLiteralTAction(SetTo, IndirectObject) |
| | 2344 | ; |
| | 2345 | |
| | 2346 | DefineTAction(TypeOn) |
| | 2347 | ; |
| | 2348 | |
| | 2349 | DefineLiteralTAction(TypeLiteralOn, DirectObject) |
| | 2350 | ; |
| | 2351 | |
| | 2352 | DefineLiteralTAction(EnterOn, DirectObject) |
| | 2353 | ; |
| | 2354 | |
| | 2355 | DefineTAction(Consult) |
| | 2356 | ; |
| | 2357 | |
| | 2358 | DefineTopicTAction(ConsultAbout, IndirectObject) |
| | 2359 | getDefaultDobj(np, resolver) |
| | 2360 | { |
| | 2361 | local actor; |
| | 2362 | local obj; |
| | 2363 | |
| | 2364 | /* |
| | 2365 | * if the actor has consulted something before, and that object |
| | 2366 | * is still visible, use it as the default this consultation |
| | 2367 | */ |
| | 2368 | actor = resolver.getTargetActor(); |
| | 2369 | obj = actor.lastConsulted; |
| | 2370 | if (obj != nil && actor.canSee(obj)) |
| | 2371 | return [new ResolveInfo(obj, DefaultObject)]; |
| | 2372 | else |
| | 2373 | return inherited(np, resolver); |
| | 2374 | } |
| | 2375 | |
| | 2376 | /* |
| | 2377 | * Filter the topic phrase resolution. If we know our direct object, |
| | 2378 | * and it's a Consultable, refer the resolution to the Consultable. |
| | 2379 | */ |
| | 2380 | filterTopic(lst, np, resolver) |
| | 2381 | { |
| | 2382 | local dobj; |
| | 2383 | |
| | 2384 | /* check the direct object */ |
| | 2385 | if (dobjList_ != nil |
| | 2386 | && dobjList_.length() == 1 |
| | 2387 | && (dobj = dobjList_[1].obj_).ofKind(Consultable)) |
| | 2388 | { |
| | 2389 | /* |
| | 2390 | * we have a Consultable direct object - let it handle the |
| | 2391 | * topic phrase resolution |
| | 2392 | */ |
| | 2393 | return dobj.resolveConsultTopic(lst, topicMatch, resolver); |
| | 2394 | } |
| | 2395 | else |
| | 2396 | { |
| | 2397 | /* otherwise, use the default handling */ |
| | 2398 | return inherited(lst, np, resolver); |
| | 2399 | } |
| | 2400 | } |
| | 2401 | ; |
| | 2402 | |
| | 2403 | DefineTAction(Switch) |
| | 2404 | ; |
| | 2405 | |
| | 2406 | DefineTAction(Flip) |
| | 2407 | ; |
| | 2408 | |
| | 2409 | DefineTAction(TurnOn) |
| | 2410 | ; |
| | 2411 | |
| | 2412 | DefineTAction(TurnOff) |
| | 2413 | ; |
| | 2414 | |
| | 2415 | DefineTAction(Light) |
| | 2416 | ; |
| | 2417 | |
| | 2418 | DefineTAction(Burn) |
| | 2419 | ; |
| | 2420 | |
| | 2421 | DefineTIAction(BurnWith) |
| | 2422 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2423 | getAllIobj(actor, scopeList) |
| | 2424 | { |
| | 2425 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2426 | } |
| | 2427 | |
| | 2428 | /* resolve the direct object first */ |
| | 2429 | resolveFirst = DirectObject |
| | 2430 | ; |
| | 2431 | |
| | 2432 | DefineTAction(Extinguish) |
| | 2433 | ; |
| | 2434 | |
| | 2435 | DefineTIAction(AttachTo) |
| | 2436 | ; |
| | 2437 | |
| | 2438 | DefineTIAction(DetachFrom) |
| | 2439 | ; |
| | 2440 | |
| | 2441 | DefineTAction(Detach) |
| | 2442 | ; |
| | 2443 | |
| | 2444 | DefineTAction(Break) |
| | 2445 | ; |
| | 2446 | |
| | 2447 | DefineTAction(Cut) |
| | 2448 | ; |
| | 2449 | |
| | 2450 | DefineTIAction(CutWith) |
| | 2451 | ; |
| | 2452 | |
| | 2453 | DefineTAction(Climb) |
| | 2454 | ; |
| | 2455 | |
| | 2456 | DefineTAction(ClimbUp) |
| | 2457 | ; |
| | 2458 | |
| | 2459 | DefineTAction(ClimbDown) |
| | 2460 | ; |
| | 2461 | |
| | 2462 | DefineTAction(Open) |
| | 2463 | ; |
| | 2464 | |
| | 2465 | DefineTAction(Close) |
| | 2466 | ; |
| | 2467 | |
| | 2468 | DefineTAction(Lock) |
| | 2469 | ; |
| | 2470 | |
| | 2471 | DefineTAction(Unlock) |
| | 2472 | ; |
| | 2473 | |
| | 2474 | DefineTIAction(LockWith) |
| | 2475 | /* |
| | 2476 | * Resolve the direct object (the lock) first, so that we know what |
| | 2477 | * we're trying to unlock when we're verifying the key. This allows |
| | 2478 | * us to (optionally) boost the likelihood of a known good key for |
| | 2479 | * disambiguation. |
| | 2480 | */ |
| | 2481 | resolveFirst = DirectObject |
| | 2482 | |
| | 2483 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2484 | getAllIobj(actor, scopeList) |
| | 2485 | { |
| | 2486 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2487 | } |
| | 2488 | ; |
| | 2489 | |
| | 2490 | DefineTIAction(UnlockWith) |
| | 2491 | /* resolve the direct object first, for the same reason as in LockWith */ |
| | 2492 | resolveFirst = DirectObject |
| | 2493 | |
| | 2494 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2495 | getAllIobj(actor, scopeList) |
| | 2496 | { |
| | 2497 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2498 | } |
| | 2499 | ; |
| | 2500 | |
| | 2501 | DefineTAction(Eat) |
| | 2502 | ; |
| | 2503 | |
| | 2504 | DefineTAction(Drink) |
| | 2505 | ; |
| | 2506 | |
| | 2507 | DefineTAction(Pour) |
| | 2508 | ; |
| | 2509 | |
| | 2510 | DefineTIAction(PourInto) |
| | 2511 | ; |
| | 2512 | |
| | 2513 | DefineTIAction(PourOnto) |
| | 2514 | ; |
| | 2515 | |
| | 2516 | DefineTAction(Clean) |
| | 2517 | ; |
| | 2518 | |
| | 2519 | DefineTIAction(CleanWith) |
| | 2520 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2521 | getAllIobj(actor, scopeList) |
| | 2522 | { |
| | 2523 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2524 | } |
| | 2525 | ; |
| | 2526 | |
| | 2527 | DefineIAction(Sit) |
| | 2528 | execAction() |
| | 2529 | { |
| | 2530 | /* |
| | 2531 | * if the actor is already sitting, just say so; otherwise, ask |
| | 2532 | * what they want to sit on |
| | 2533 | */ |
| | 2534 | if (gActor.posture == sitting) |
| | 2535 | reportFailure(&alreadySittingMsg); |
| | 2536 | else |
| | 2537 | askForDobj(SitOn); |
| | 2538 | } |
| | 2539 | ; |
| | 2540 | |
| | 2541 | DefineTAction(SitOn) |
| | 2542 | ; |
| | 2543 | |
| | 2544 | DefineIAction(Lie) |
| | 2545 | execAction() |
| | 2546 | { |
| | 2547 | /* |
| | 2548 | * if the actor is already lying down, just say so; otherwise, |
| | 2549 | * ask what they want to lie on |
| | 2550 | */ |
| | 2551 | if (gActor.posture == lying) |
| | 2552 | reportFailure(&alreadyLyingMsg); |
| | 2553 | else |
| | 2554 | askForDobj(LieOn); |
| | 2555 | } |
| | 2556 | ; |
| | 2557 | |
| | 2558 | DefineTAction(LieOn) |
| | 2559 | ; |
| | 2560 | |
| | 2561 | DefineTAction(StandOn) |
| | 2562 | ; |
| | 2563 | |
| | 2564 | DefineIAction(Stand) |
| | 2565 | execAction() |
| | 2566 | { |
| | 2567 | /* let the actor handle it */ |
| | 2568 | gActor.standUp(); |
| | 2569 | } |
| | 2570 | ; |
| | 2571 | |
| | 2572 | DefineTAction(Board) |
| | 2573 | ; |
| | 2574 | |
| | 2575 | DefineTAction(GetOutOf) |
| | 2576 | getAllDobj(actor, scopeList) |
| | 2577 | { |
| | 2578 | /* 'all' for 'get out of' is the actor's immediate container */ |
| | 2579 | return scopeList.subset({x: actor.isDirectlyIn(x)}); |
| | 2580 | } |
| | 2581 | ; |
| | 2582 | |
| | 2583 | DefineTAction(GetOffOf) |
| | 2584 | getAllDobj(actor, scopeList) |
| | 2585 | { |
| | 2586 | /* 'all' for 'get off of' is the actor's immediate container */ |
| | 2587 | return scopeList.subset({x: actor.isDirectlyIn(x)}); |
| | 2588 | } |
| | 2589 | ; |
| | 2590 | |
| | 2591 | DefineIAction(GetOut) |
| | 2592 | execAction() |
| | 2593 | { |
| | 2594 | /* let the actor handle it */ |
| | 2595 | gActor.disembark(); |
| | 2596 | } |
| | 2597 | ; |
| | 2598 | |
| | 2599 | DefineTAction(Fasten) |
| | 2600 | ; |
| | 2601 | |
| | 2602 | DefineTIAction(FastenTo) |
| | 2603 | ; |
| | 2604 | |
| | 2605 | DefineTAction(Unfasten) |
| | 2606 | ; |
| | 2607 | |
| | 2608 | DefineTIAction(UnfastenFrom) |
| | 2609 | ; |
| | 2610 | |
| | 2611 | DefineTAction(PlugIn) |
| | 2612 | ; |
| | 2613 | |
| | 2614 | DefineTIAction(PlugInto) |
| | 2615 | ; |
| | 2616 | |
| | 2617 | DefineTAction(Unplug) |
| | 2618 | ; |
| | 2619 | |
| | 2620 | DefineTIAction(UnplugFrom) |
| | 2621 | ; |
| | 2622 | |
| | 2623 | DefineTAction(Screw) |
| | 2624 | ; |
| | 2625 | |
| | 2626 | DefineTIAction(ScrewWith) |
| | 2627 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2628 | getAllIobj(actor, scopeList) |
| | 2629 | { |
| | 2630 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2631 | } |
| | 2632 | ; |
| | 2633 | |
| | 2634 | DefineTAction(Unscrew) |
| | 2635 | ; |
| | 2636 | |
| | 2637 | DefineTIAction(UnscrewWith) |
| | 2638 | /* limit 'all' for the indirect object to items in inventory */ |
| | 2639 | getAllIobj(actor, scopeList) |
| | 2640 | { |
| | 2641 | return scopeList.subset({x: x.isIn(actor)}); |
| | 2642 | } |
| | 2643 | ; |
| | 2644 | |
| | 2645 | /* ------------------------------------------------------------------------ */ |
| | 2646 | /* |
| | 2647 | * Travel Action - this is the base class for verbs that attempt to move |
| | 2648 | * an actor to a new location via one of the directional connections |
| | 2649 | * from the current location. |
| | 2650 | * |
| | 2651 | * Each grammar rule for this action must set the 'dirMatch' property to |
| | 2652 | * a DirectionProd match object that gives the direction. |
| | 2653 | */ |
| | 2654 | DefineIAction(Travel) |
| | 2655 | execAction() |
| | 2656 | { |
| | 2657 | local conn; |
| | 2658 | |
| | 2659 | /* |
| | 2660 | * Perform the travel via the connector, if we have one. If |
| | 2661 | * there's no connector defined for this direction, show a |
| | 2662 | * default "you can't go that way" message. |
| | 2663 | */ |
| | 2664 | if ((conn = getConnector()) != nil) |
| | 2665 | { |
| | 2666 | /* |
| | 2667 | * we have a connector - use the pseudo-action TravelVia with |
| | 2668 | * the connector to carry out the travel |
| | 2669 | */ |
| | 2670 | replaceAction(TravelVia, conn); |
| | 2671 | } |
| | 2672 | else |
| | 2673 | { |
| | 2674 | /* no connector - show a default "can't go that way" error */ |
| | 2675 | mainReport(&cannotGoThatWayMsg); |
| | 2676 | } |
| | 2677 | } |
| | 2678 | |
| | 2679 | /* get the direction object for the travel */ |
| | 2680 | getDirection() { return dirMatch != nil ? dirMatch.dir : nil; } |
| | 2681 | |
| | 2682 | /* |
| | 2683 | * Get my travel connector. My connector is given by the travel |
| | 2684 | * link property for this action as defined in the actor's current |
| | 2685 | * location. |
| | 2686 | */ |
| | 2687 | getConnector() |
| | 2688 | { |
| | 2689 | /* ask the location for the connector in my direction */ |
| | 2690 | return gActor.location == nil |
| | 2691 | ? nil |
| | 2692 | : gActor.location.getTravelConnector(getDirection(), gActor); |
| | 2693 | } |
| | 2694 | |
| | 2695 | /* |
| | 2696 | * The grammar rules for the individual directions will usually just |
| | 2697 | * create a base TravelAction object, rather than one of the |
| | 2698 | * direction-specific subclasses (NorthAction, etc). For |
| | 2699 | * convenience in testing the action, though, treat ourself as |
| | 2700 | * matching the subclass with the same direction. |
| | 2701 | */ |
| | 2702 | actionOfKind(cls) |
| | 2703 | { |
| | 2704 | /* |
| | 2705 | * If they're asking about a specific-direction TravelAction |
| | 2706 | * subclass, then we match it if our own direction matches that |
| | 2707 | * of the given subclass, and we fail to match if our direction |
| | 2708 | * doesn't match the given direction. |
| | 2709 | */ |
| | 2710 | if (cls.ofKind(TravelAction) && cls.getDirection() != nil) |
| | 2711 | { |
| | 2712 | /* we match if and only if the direction matches */ |
| | 2713 | return (getDirection() == cls.getDirection()); |
| | 2714 | } |
| | 2715 | |
| | 2716 | /* otherwise, inherit the default handling */ |
| | 2717 | return inherited(cls); |
| | 2718 | } |
| | 2719 | ; |
| | 2720 | |
| | 2721 | /* for a vague command such as GO, which doesn't say where to go */ |
| | 2722 | DefineIAction(VagueTravel) |
| | 2723 | execAction() |
| | 2724 | { |
| | 2725 | /* simply ask for a direction */ |
| | 2726 | reportFailure(&whereToGoMsg); |
| | 2727 | } |
| | 2728 | ; |
| | 2729 | |
| | 2730 | /* |
| | 2731 | * This class makes it convenient to synthesize a TravelAction given a |
| | 2732 | * Direction object. To create a travel action for a direction, use |
| | 2733 | * |
| | 2734 | *. new TravelDirAction(direction) |
| | 2735 | * |
| | 2736 | * where 'direction' is the direction object (northDirection, etc) for |
| | 2737 | * the desired direction of travel. Note that if you want to use the |
| | 2738 | * resulting object in replaceAction() or one of the similar macros, |
| | 2739 | * you'll need to go directly to the underlying function rather than |
| | 2740 | * using the standard macro, since the macros expect a literal action |
| | 2741 | * name rather than an object. For example: |
| | 2742 | * |
| | 2743 | *. _replaceAction(gActor, new TravelDirAction(getDirection)); |
| | 2744 | */ |
| | 2745 | DefineAction(TravelDir, TravelAction) |
| | 2746 | construct(dir) |
| | 2747 | { |
| | 2748 | /* remember my direction */ |
| | 2749 | dir_ = dir; |
| | 2750 | } |
| | 2751 | |
| | 2752 | /* get my direction */ |
| | 2753 | getDirection() { return dir_; } |
| | 2754 | |
| | 2755 | /* my direction, normally specified during construction */ |
| | 2756 | dir_ = nil |
| | 2757 | ; |
| | 2758 | |
| | 2759 | /* |
| | 2760 | * To make it more convenient to use directional travel actions as |
| | 2761 | * synthesized commands, define a set of action classes for the specific |
| | 2762 | * directions. |
| | 2763 | */ |
| | 2764 | DefineAction(North, TravelAction) |
| | 2765 | getDirection = northDirection |
| | 2766 | ; |
| | 2767 | DefineAction(South, TravelAction) |
| | 2768 | getDirection = southDirection |
| | 2769 | ; |
| | 2770 | DefineAction(East, TravelAction) |
| | 2771 | getDirection = eastDirection |
| | 2772 | ; |
| | 2773 | DefineAction(West, TravelAction) |
| | 2774 | getDirection = westDirection |
| | 2775 | ; |
| | 2776 | DefineAction(Northeast, TravelAction) |
| | 2777 | getDirection = northeastDirection |
| | 2778 | ; |
| | 2779 | DefineAction(Northwest, TravelAction) |
| | 2780 | getDirection = northwestDirection |
| | 2781 | ; |
| | 2782 | DefineAction(Southeast, TravelAction) |
| | 2783 | getDirection = southeastDirection |
| | 2784 | ; |
| | 2785 | DefineAction(Southwest, TravelAction) |
| | 2786 | getDirection = southwestDirection |
| | 2787 | ; |
| | 2788 | DefineAction(In, TravelAction) |
| | 2789 | getDirection = inDirection |
| | 2790 | ; |
| | 2791 | DefineAction(Out, TravelAction) |
| | 2792 | getDirection = outDirection |
| | 2793 | ; |
| | 2794 | DefineAction(Up, TravelAction) |
| | 2795 | getDirection = upDirection |
| | 2796 | ; |
| | 2797 | DefineAction(Down, TravelAction) |
| | 2798 | getDirection = downDirection |
| | 2799 | ; |
| | 2800 | DefineAction(Fore, TravelAction) |
| | 2801 | getDirection = foreDirection |
| | 2802 | ; |
| | 2803 | DefineAction(Aft, TravelAction) |
| | 2804 | getDirection = aftDirection |
| | 2805 | ; |
| | 2806 | DefineAction(Port, TravelAction) |
| | 2807 | getDirection = portDirection |
| | 2808 | ; |
| | 2809 | DefineAction(Starboard, TravelAction) |
| | 2810 | getDirection = starboardDirection |
| | 2811 | ; |
| | 2812 | |
| | 2813 | /* |
| | 2814 | * Non-directional travel actions |
| | 2815 | */ |
| | 2816 | |
| | 2817 | DefineTAction(GoThrough) |
| | 2818 | ; |
| | 2819 | |
| | 2820 | DefineTAction(Enter) |
| | 2821 | ; |
| | 2822 | |
| | 2823 | /* |
| | 2824 | * An internal action for traveling via a connector. This isn't a real |
| | 2825 | * action, and shouldn't have a grammar defined for it. The purpose of |
| | 2826 | * this action is to allow real actions that cause travel via a |
| | 2827 | * connector to be implemented by mapping to this internal action, which |
| | 2828 | * we implement on the base travel connector class. |
| | 2829 | */ |
| | 2830 | DefineTAction(TravelVia) |
| | 2831 | /* |
| | 2832 | * The direct object of this synthetic action isn't necessarily an |
| | 2833 | * ordinary simulation object: it could be a TravelConnector instead. |
| | 2834 | * Since callers asking for a direct object almost always expect a |
| | 2835 | * simulation object, returning a non-simulation object here can be |
| | 2836 | * problematic. To avoid this, we return an empty object list by |
| | 2837 | * default - this ensures that no one who asks for the direct object |
| | 2838 | * of the verb will get back a non-simulation travel connector. |
| | 2839 | */ |
| | 2840 | getCurrentObjects = [] |
| | 2841 | ; |
| | 2842 | |
| | 2843 | /* "go back" */ |
| | 2844 | DefineIAction(GoBack) |
| | 2845 | execAction() |
| | 2846 | { |
| | 2847 | /* ask the actor to handle it */ |
| | 2848 | gActor.reverseLastTravel(); |
| | 2849 | } |
| | 2850 | ; |
| | 2851 | |
| | 2852 | /* ------------------------------------------------------------------------ */ |
| | 2853 | /* |
| | 2854 | * Combined pushing-and-traveling action ("push crate north", "drag sled |
| | 2855 | * into cave"). All of these are based on a base action class, which |
| | 2856 | * defines the methods invoked on the object being pushed; the |
| | 2857 | * subclasses provide a definition of the connector that determines |
| | 2858 | * where the travel takes us. |
| | 2859 | */ |
| | 2860 | DefineTAction(PushTravel) |
| | 2861 | /* |
| | 2862 | * Carry out the nested travel action for the special combination |
| | 2863 | * push-traveler. This should carry out the same action we would |
| | 2864 | * have performed for the underlying basic travel. |
| | 2865 | * |
| | 2866 | * This method is invoked by the TravelPushable to carry out a |
| | 2867 | * push-travel action. The TravelPushable object will first set up |
| | 2868 | * a PushTraveler as the actor's global traveler, and it will then |
| | 2869 | * invoke this method to carry out the actual travel with that |
| | 2870 | * special traveler in effect. Our job is to provide the mapping to |
| | 2871 | * the correct underlying simple travel action; since we'll be |
| | 2872 | * moving the PushTraveler object, we can move it using the ordinary |
| | 2873 | * non-push travel action as though it were any other traveler. |
| | 2874 | * |
| | 2875 | * This method is abstract - each subclass must define it |
| | 2876 | * appropriately. |
| | 2877 | */ |
| | 2878 | // performTravel() { } |
| | 2879 | ; |
| | 2880 | |
| | 2881 | /* |
| | 2882 | * For directional push-and-travel commands, we define a common base |
| | 2883 | * class that does the work to find the connector based on the room's |
| | 2884 | * directional connector. |
| | 2885 | * |
| | 2886 | * Subclasses for grammar rules must define the 'dirMatch' property to |
| | 2887 | * be a DirectionProd object for the associated direction. |
| | 2888 | */ |
| | 2889 | DefineAction(PushTravelDir, PushTravelAction) |
| | 2890 | /* |
| | 2891 | * Get the direction we're going. By default, we return the |
| | 2892 | * direction associated with the dirMatch match object from our |
| | 2893 | * grammar match. |
| | 2894 | */ |
| | 2895 | getDirection() { return dirMatch.dir; } |
| | 2896 | |
| | 2897 | /* carry out the nested travel action for a PushTravel */ |
| | 2898 | performTravel() |
| | 2899 | { |
| | 2900 | local conn; |
| | 2901 | |
| | 2902 | /* ask the actor's location for the connector in our direction */ |
| | 2903 | conn = gActor.location.getTravelConnector(getDirection(), gActor); |
| | 2904 | |
| | 2905 | /* perform a nested TravelVia on the connector */ |
| | 2906 | nestedAction(TravelVia, conn); |
| | 2907 | } |
| | 2908 | ; |
| | 2909 | |
| | 2910 | /* |
| | 2911 | * To make it easy to synthesize actions for pushing objects, define |
| | 2912 | * individual subclasses for the various directions. |
| | 2913 | */ |
| | 2914 | DefineAction(PushNorth, PushTravelDirAction) |
| | 2915 | getDirection = northDirection |
| | 2916 | ; |
| | 2917 | |
| | 2918 | DefineAction(PushSouth, PushTravelDirAction) |
| | 2919 | getDirection = southDirection |
| | 2920 | ; |
| | 2921 | |
| | 2922 | DefineAction(PushEast, PushTravelDirAction) |
| | 2923 | getDirection = eastDirection |
| | 2924 | ; |
| | 2925 | |
| | 2926 | DefineAction(PushWest, PushTravelDirAction) |
| | 2927 | getDirection = westDirection |
| | 2928 | ; |
| | 2929 | |
| | 2930 | DefineAction(PushNorthwest, PushTravelDirAction) |
| | 2931 | getDirection = northwestDirection |
| | 2932 | ; |
| | 2933 | |
| | 2934 | DefineAction(PushNortheast, PushTravelDirAction) |
| | 2935 | getDirection = northeastDirection |
| | 2936 | ; |
| | 2937 | |
| | 2938 | DefineAction(PushSouthwest, PushTravelDirAction) |
| | 2939 | getDirection = southwestDirection |
| | 2940 | ; |
| | 2941 | |
| | 2942 | DefineAction(PushSoutheast, PushTravelDirAction) |
| | 2943 | getDirection = southeastDirection |
| | 2944 | ; |
| | 2945 | |
| | 2946 | DefineAction(PushUp, PushTravelDirAction) |
| | 2947 | getDirection = upDirection |
| | 2948 | ; |
| | 2949 | |
| | 2950 | DefineAction(PushDown, PushTravelDirAction) |
| | 2951 | getDirection = downDirection |
| | 2952 | ; |
| | 2953 | |
| | 2954 | DefineAction(PushIn, PushTravelDirAction) |
| | 2955 | getDirection = inDirection |
| | 2956 | ; |
| | 2957 | |
| | 2958 | DefineAction(PushOut, PushTravelDirAction) |
| | 2959 | getDirection = outDirection |
| | 2960 | ; |
| | 2961 | |
| | 2962 | DefineAction(PushFore, PushTravelDirAction) |
| | 2963 | getDirection = foreDirection |
| | 2964 | ; |
| | 2965 | |
| | 2966 | DefineAction(PushAft, PushTravelDirAction) |
| | 2967 | getDirection = aftDirection |
| | 2968 | ; |
| | 2969 | |
| | 2970 | DefineAction(PushPort, PushTravelDirAction) |
| | 2971 | getDirection = portDirection |
| | 2972 | ; |
| | 2973 | |
| | 2974 | DefineAction(PushStarboard, PushTravelDirAction) |
| | 2975 | getDirection = starboardDirection |
| | 2976 | ; |
| | 2977 | |
| | 2978 | /* |
| | 2979 | * Base class for two-object push-travel commands, such as "push boulder |
| | 2980 | * out of cave" or "drag sled up hill". For all of these, the connector |
| | 2981 | * is given by the indirect object. |
| | 2982 | */ |
| | 2983 | DefineAction(PushTravelViaIobj, TIAction, PushTravelAction) |
| | 2984 | /* |
| | 2985 | * Verify the indirect object of the push-travel action. We'll |
| | 2986 | * remap this to given corresponding simple travel action, and call |
| | 2987 | * that action's verifier. |
| | 2988 | */ |
| | 2989 | verifyPushTravelIobj(obj, action) |
| | 2990 | { |
| | 2991 | /* handle this by remapping it to the underlying simple action */ |
| | 2992 | remapVerify(IndirectObject, gVerifyResults, [action, obj]); |
| | 2993 | } |
| | 2994 | ; |
| | 2995 | |
| | 2996 | DefineTIActionSub(PushTravelThrough, PushTravelViaIobjAction) |
| | 2997 | /* |
| | 2998 | * Carry out the underlying simple travel action. This simply |
| | 2999 | * performs a GoThrough on my indirect object, as though we had |
| | 3000 | * typed simply GO THROUGH iobj. The PushTraveler will already be |
| | 3001 | * set up as the actor's special traveler, so the ordinary GO |
| | 3002 | * THROUGH command will move the special PushTraveler object as |
| | 3003 | * though it were the original actor. |
| | 3004 | */ |
| | 3005 | performTravel() { nestedAction(GoThrough, getIobj()); } |
| | 3006 | ; |
| | 3007 | |
| | 3008 | DefineTIActionSub(PushTravelEnter, PushTravelViaIobjAction) |
| | 3009 | /* carry out the underlying simple travel as an ENTER action */ |
| | 3010 | performTravel() { nestedAction(Enter, getIobj()); } |
| | 3011 | ; |
| | 3012 | |
| | 3013 | DefineTIActionSub(PushTravelGetOutOf, PushTravelViaIobjAction) |
| | 3014 | /* carry out the underlying simple travel as a GET OUT OF action */ |
| | 3015 | performTravel() { nestedAction(GetOutOf, getIobj()); } |
| | 3016 | ; |
| | 3017 | |
| | 3018 | DefineTIActionSub(PushTravelClimbUp, PushTravelViaIobjAction) |
| | 3019 | /* carry out the underlying simple travel as a CLIMB UP action */ |
| | 3020 | performTravel() { nestedAction(ClimbUp, getIobj()); } |
| | 3021 | ; |
| | 3022 | |
| | 3023 | DefineTIActionSub(PushTravelClimbDown, PushTravelViaIobjAction) |
| | 3024 | /* carry out the underlying simple travel as an CLIMB DOWN action */ |
| | 3025 | performTravel() { nestedAction(ClimbDown, getIobj()); } |
| | 3026 | ; |
| | 3027 | |
| | 3028 | /* |
| | 3029 | * The "exits" verb. This verb explicitly shows all of the exits from |
| | 3030 | * the current location. |
| | 3031 | */ |
| | 3032 | DefineIAction(Exits) |
| | 3033 | execAction() |
| | 3034 | { |
| | 3035 | /* |
| | 3036 | * if we have an exit lister object, invoke it; otherwise, |
| | 3037 | * explain that this command isn't supported in this game |
| | 3038 | */ |
| | 3039 | if (gExitLister != nil) |
| | 3040 | gExitLister.showExitsCommand(); |
| | 3041 | else |
| | 3042 | gLibMessages.commandNotPresent; |
| | 3043 | } |
| | 3044 | ; |
| | 3045 | |
| | 3046 | /* in case the exits module isn't included */ |
| | 3047 | property showExitsCommand, exitsOnOffCommand; |
| | 3048 | |
| | 3049 | /* |
| | 3050 | * Change the exit display mode. The grammar must set one of the mode |
| | 3051 | * token properties to a non-nil value, according to which mode the |
| | 3052 | * player selected: on_ for turning on statusline and description lists; |
| | 3053 | * stat_ for turning on only the statusline list; look_ for turning on |
| | 3054 | * only the room description list; and off_ for turning off everything. |
| | 3055 | */ |
| | 3056 | DefineSystemAction(ExitsMode) |
| | 3057 | execSystemAction() |
| | 3058 | { |
| | 3059 | local stat, look; |
| | 3060 | |
| | 3061 | /* |
| | 3062 | * If it's EXITS ON, turn on both statusline and room description |
| | 3063 | * lists. If it's EXITS LOOK or EXITS STATUS, just turn on one |
| | 3064 | * or the other. Otherwise, turn both off. |
| | 3065 | */ |
| | 3066 | stat = (stat_ != nil || on_ != nil); |
| | 3067 | look = (look_ != nil || on_ != nil); |
| | 3068 | |
| | 3069 | /* update the exit display */ |
| | 3070 | if (gExitLister != nil) |
| | 3071 | gExitLister.exitsOnOffCommand(stat, look); |
| | 3072 | else |
| | 3073 | gLibMessages.commandNotPresent; |
| | 3074 | } |
| | 3075 | ; |
| | 3076 | |
| | 3077 | /* |
| | 3078 | * Dummy OOPS action for times when OOPS isn't in context. We'll simply |
| | 3079 | * explain how OOPS works, and that you can't use it right now. |
| | 3080 | */ |
| | 3081 | DefineLiteralAction(Oops) |
| | 3082 | execAction() |
| | 3083 | { |
| | 3084 | /* simply explain how this command works */ |
| | 3085 | gLibMessages.oopsOutOfContext; |
| | 3086 | } |
| | 3087 | |
| | 3088 | /* this is a meta-command, so don't consume any time */ |
| | 3089 | actionTime = 0 |
| | 3090 | ; |
| | 3091 | |
| | 3092 | /* intransitive form of "oops" */ |
| | 3093 | DefineIAction(OopsI) |
| | 3094 | doActionMain() |
| | 3095 | { |
| | 3096 | /* as with OOPS with a literal, simply explain the problem */ |
| | 3097 | gLibMessages.oopsOutOfContext; |
| | 3098 | } |
| | 3099 | |
| | 3100 | /* this is a meta-command, so don't consume any time */ |
| | 3101 | actionTime = 0 |
| | 3102 | ; |
| | 3103 | |
| | 3104 | property disableHints, showHints; |
| | 3105 | |
| | 3106 | /* hint system - disable hints for this session */ |
| | 3107 | DefineSystemAction(HintsOff) |
| | 3108 | execSystemAction() |
| | 3109 | { |
| | 3110 | if (gHintManager != nil) |
| | 3111 | gHintManager.disableHints(); |
| | 3112 | else |
| | 3113 | mainReport(gLibMessages.hintsNotPresent); |
| | 3114 | } |
| | 3115 | ; |
| | 3116 | |
| | 3117 | /* invoke hint system */ |
| | 3118 | DefineSystemAction(Hint) |
| | 3119 | execSystemAction() |
| | 3120 | { |
| | 3121 | if (gHintManager != nil) |
| | 3122 | gHintManager.showHints(); |
| | 3123 | else |
| | 3124 | mainReport(gLibMessages.hintsNotPresent); |
| | 3125 | } |
| | 3126 | ; |
| | 3127 | |
| | 3128 | /* ------------------------------------------------------------------------ */ |
| | 3129 | /* |
| | 3130 | * Parser debugging verbs |
| | 3131 | */ |
| | 3132 | |
| | 3133 | #ifdef PARSER_DEBUG |
| | 3134 | |
| | 3135 | DefineIAction(ParseDebug) |
| | 3136 | execAction() |
| | 3137 | { |
| | 3138 | local newMode; |
| | 3139 | |
| | 3140 | /* |
| | 3141 | * get the mode - if the mode is explicitly stated in the |
| | 3142 | * command, use the stated new mode, otherwise invert the current |
| | 3143 | * mode |
| | 3144 | */ |
| | 3145 | newMode = (onOrOff_ == 'on' |
| | 3146 | ? true |
| | 3147 | : onOrOff_ == 'off' |
| | 3148 | ? nil |
| | 3149 | : !libGlobal.parserDebugMode); |
| | 3150 | |
| | 3151 | /* set the new mode */ |
| | 3152 | libGlobal.parserDebugMode = newMode; |
| | 3153 | |
| | 3154 | /* mention the change */ |
| | 3155 | "Parser debugging is now |
| | 3156 | <<libGlobal.parserDebugMode ? 'on' : 'off'>>.\n"; |
| | 3157 | } |
| | 3158 | ; |
| | 3159 | |
| | 3160 | grammar predicate(ParseDebug): |
| | 3161 | 'parse-debug' 'on'->onOrOff_ |
| | 3162 | | 'parse-debug' 'off'->onOrOff_ |
| | 3163 | | 'parse-debug' |
| | 3164 | : ParseDebugAction |
| | 3165 | ; |
| | 3166 | |
| | 3167 | #endif |
| | 3168 | |