actions.t | documentation |
#charset "us-ascii" /* * Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved. * * TADS 3 Library: Actions. * * This module defines the set of built-in library actions. */ #include "adv3.h" #include "tok.h" /* ------------------------------------------------------------------------ */ /* * Special "debug" action - this simply breaks into the debugger, if the * debugger is present. */ DefineIAction(Debug) execAction() { /* if the debugger is present, break into it */ if (t3DebugTrace(T3DebugCheck)) t3DebugTrace(T3DebugBreak); else "Debugger not present. "; } ; /* ------------------------------------------------------------------------ */ /* * Special internal action to note a change to the darkness level. This * command is invoked internally when a change to the darkness level * occurs. */ DefineIAction(NoteDarkness) execAction() { /* * if we're in the dark, note that darkness has fallen; * otherwise, show the player character's room description as * though the player had typed "look" */ if (gActor.isLocationLit()) { /* look around */ gActor.lookAround(true); } else { /* it is now dark */ mainReport(&newlyDarkMsg); } } /* this is an internal command that takes no time */ actionTime = 0 /* this isn't a real action, so it's not repeatable */ isRepeatable = nil /* this action doesn't do anything; don't include it in undo */ includeInUndo = nil ; /* ------------------------------------------------------------------------ */ /* * Special "again" action. This command repeats the previous command. */ DefineIAction(Again) /* for obvious reasons, 'again' is not itself repeatable with 'again' */ isRepeatable = nil /* * the undo command itself is not undoable (but the underlying * command that we repeat might be) */ includeInUndo = nil /* information on the most recent command */ lastIssuingActor = nil lastTargetActor = nil lastTargetActorPhrase = nil lastAction = nil /* save the most recent command so that it can be repeated if desired */ saveForAgain(issuingActor, targetActor, targetActorPhrase, action) { /* save the information */ lastIssuingActor = issuingActor; lastTargetActor = targetActor; lastTargetActorPhrase = targetActorPhrase; lastAction = action.createClone(); } /* forget the last command, so that AGAIN cannot be used */ clearForAgain() { lastAction = nil; } /* * Execute the 'again' command. This action is special enough that * we override its entire action processing sequence - this is * necessary in case we're repeating another special command, such * as 'again', and in any case is desirable because we don't want * 'again' to count as a command in its own right; it's essentially * just a macro that we replace with the original command. */ doAction(issuingActor, targetActor, targetActorPhrase, countsAsIssuerTurn) { /* if there's nothing to repeat, show an error and give up */ if (lastAction == nil) { gLibMessages.noCommandForAgain(); return; } /* * 'again' cannot be executed with a target actor - the target * actor must be the player character */ if (!targetActor.isPlayerChar) { gLibMessages.againCannotChangeActor(); return; } /* * if the issuing actor isn't the same as the target actor, make * sure the issuer can still talk to the target */ if (lastIssuingActor != lastTargetActor && !lastIssuingActor.canTalkTo(lastTargetActor)) { /* complain that we can no longer talk to the target */ gLibMessages.againCannotTalkToTarget( lastIssuingActor, lastTargetActor); return; } /* * If the last issuing actor is different from the last target * actor, then the command counts as an issuer turn, because * we're effectively repeating the entire last command, including * the target actor specification. That is, after a command like * "bob, go east", saying "again" is just like saying "bob, go * east" again, which counts as an issuer turn. */ if (lastTargetActor != lastIssuingActor) countsAsIssuerTurn = true; /* reset any cached information for the new command context */ lastAction.resetAction(); /* repeat the action */ lastAction.repeatAction(lastTargetActor, lastTargetActorPhrase, lastIssuingActor, countsAsIssuerTurn); /* * If the command was directed from the issuer to a different * target actor, and the issuer wants to wait for the full set of * issued commands to complete before getting another turn, tell * the issuer to begin waiting. */ if (lastTargetActor != lastIssuingActor) lastIssuingActor.waitForIssuedCommand(lastTargetActor); } /* * this command itself consumes no time on the game clock (although * the action we perform might) */ actionTime = 0 ; /* ------------------------------------------------------------------------ */ /* * PreSaveObject - every instance of this class is notified, via its * execute() method, just before we save the game. This uses the * ModuleExecObject framework, so the sequencing lists (execBeforeMe, * execAfterMe) can be used to control relative ordering of execution * among instances. */ class PreSaveObject: ModuleExecObject /* * Each instance must override execute() with its specific pre-save * code. */ ; /* * PostRestoreObject - every instance of this class is notified, via its * execute() method, immediately after we restore the game. */ class PostRestoreObject: ModuleExecObject /* * note: each instance must override execute() with its post-restore * code */ /* * The "restore code," which is the (normally integer) value passed * as the second argument to restoreGame(). The restore code gives * us some idea of what triggered the restoration. By default, we * define the following restore codes: * * 1 - the system is restoring a game as part of interpreter * startup, usually because the user explicitly specified a game to * restore on the interpreter command line or via a GUI shell * mechanism, such as double-clicking on a saved game file from the * desktop. * * 2 - the user is explicitly restoring a game via a RESTORE command. * * Games and library extensions can use their own additional restore * codes in their calls to restoreGame(). */ restoreCode = nil ; /* * PreRestartObject - every instance of this class is notified, via its * execute() method, just before we restart the game (with a RESTART * command, for example). */ class PreRestartObject: ModuleExecObject /* * Each instance must override execute() with its specific * pre-restart code. */ ; /* * PostUndoObject - every instance of this class is notified, via its * execute() method, immediately after we perform an 'undo' command. */ class PostUndoObject: ModuleExecObject /* * Each instance must override execute() with its specific post-undo * code. */ ; /* ------------------------------------------------------------------------ */ /* * Special "save" action. This command saves the current game state to * an external file for later restoration. */ DefineAction(Save, FileOpAction) /* the file dialog prompt */ filePromptMsg = (gLibMessages.getSavePrompt()) /* we're asking for a file to save, or type t3-save */ fileDisposition = InFileSave fileTypeID = FileTypeT3Save /* cancel message */ showCancelMsg() { gLibMessages.saveCanceled(); } /* perform a save */ performFileOp(fname, ack, desc:?) { /* before saving the game, notify all PreSaveObject instances */ PreSaveObject.classExec(); /* * Save the game to the given file. If an error occurs, the * save routine will throw a runtime error. */ try { /* try saving the game */ saveGame(fname, gameMain.getSaveDesc(desc)); } catch (StorageServerError sse) { /* the save failed due to a storage server problem - explain */ gLibMessages.saveFailedOnServer(sse); /* done */ return; } catch (RuntimeError err) { /* the save failed - mention the problem */ gLibMessages.saveFailed(err); /* done */ return; } /* note the successful save */ gLibMessages.saveOkay(); } /* * Saving has no effect on game state, so it's irrelevant whether or * not it's undoable; but it might be confusing to say we undid a * "save" command, because the player might think we deleted the * saved file. To avoid such confusion, do not include "save" * commands in the undo log. */ includeInUndo = nil /* * Don't allow this to be repeated with AGAIN. There's no point in * repeating a SAVE immediately, as nothing will have changed in the * game state to warrant saving again. */ isRepeatable = nil ; /* * Subclass of Save action that takes a literal string as part of the * command. The filename must be a literal enclosed in quotes, and the * string (with the quotes) must be stored in our fname_ property by * assignment of a quotedStringPhrase production in the grammar rule. */ DefineAction(SaveString, SaveAction) execSystemAction() { /* * Perform the save, using the filename given in our fname_ * parameter, trimmed of quotes. */ performFileOp(fname_.getStringText(), true); } ; /* ------------------------------------------------------------------------ */ /* * Special "restore" action. This action restores game state previously * saved with the "save" action. */ DefineSystemAction(Restore) execSystemAction() { /* ask for a file and restore it */ askAndRestore(); /* * regardless of what happened, abandon any additional commands * on the same command line */ throw new TerminateCommandException(); } /* * Ask for a file and try to restore it. Returns true on success, * nil on failure. (Failure could indicate that the user chose to * cancel out of the file selector, that we couldn't find the file to * restore, or that the file isn't a valid saved state file. In any * case, we show an appropriate message on failure.) */ askAndRestore() { local succ; local result; local origElapsedTime; /* presume failure */ succ = nil; /* note the current elapsed game time */ origElapsedTime = realTimeManager.getElapsedTime(); /* ask for a file */ result = getInputFile(gLibMessages.getRestorePrompt(), InFileOpen, FileTypeT3Save, 0); /* * restore the real-time clock, so that the time spent in the * file selector dialog doesn't count against the game time */ realTimeManager.setElapsedTime(origElapsedTime); /* check the inputFile response */ switch(result[1]) { case InFileSuccess: /* * try restoring the file; use code 2 to indicate that the * restoration was performed by an explicit RESTORE command */ if (performRestore(result[2], 2)) { /* note that we succeeded */ succ = true; } else { /* * failed - in case the failed restore took some time, * restore the real-time clock, so that the file-reading * time doesn't count against the game time */ realTimeManager.setElapsedTime(origElapsedTime); } /* done */ break; case InFileFailure: /* advise of the failure of the prompt */ if (result.length() > 1) gLibMessages.filePromptFailedMsg(result[2]); else gLibMessages.filePromptFailed(); break; case InFileCancel: /* acknowledge the cancellation */ gLibMessages.restoreCanceled(); break; } /* * If we were successful, clear out the AGAIN memory. This * avoids any confusion about whether we're repeating the RESTORE * command itself, the command just before RESTORE from the * current session, or the last command before SAVE from the * restored game. */ if (succ) AgainAction.clearForAgain(); /* return the success/failure indication */ return succ; } /* * Restore a game on startup. This can be called from mainRestore() * to restore a saved game directly as part of loading the game. * (Most interpreters provide a way of starting the interpreter * directly with a saved game to be restored, skipping the * intermediate step of running the game and using a RESTORE * command.) * * Returns true on success, nil on failure. On failure, the caller * should simply exit the program. On success, the caller should * start the game running, usually using runGame(), after showing any * desired introductory messages. */ startupRestore(fname) { /* * try restoring the game, using code 1 to indicate that this is * a direct startup restore */ if (performRestore(fname, 1)) { /* success - tell the caller to proceed with the restored game */ return true; } else { /* * Failure. We've described the problem, so ask the user * what they want to do about it. */ try { /* show options and read the response */ failedRestoreOptions(); /* if we get here, proceed with the game */ return true; } catch (QuittingException qe) { /* quitting - tell the caller to terminate */ return nil; } } } /* * Restore a file. 'code' is the restoreCode value for the * PostRestoreObject notifications. Returns true on success, nil on * failure. */ performRestore(fname, code) { try { /* restore the file */ restoreGame(fname); } catch (StorageServerError sse) { /* failed due to a storage server error - explain the problem */ gLibMessages.restoreFailedOnServer(sse); /* indicate failure */ return nil; } catch (RuntimeError err) { /* failed - check the error to see what went wrong */ switch(err.errno_) { case 1201: /* not a saved state file */ gLibMessages.restoreInvalidFile(); break; case 1202: /* saved by different game or different version */ gLibMessages.restoreInvalidMatch(); break; case 1207: /* corrupted saved state file */ gLibMessages.restoreCorruptedFile(); break; default: /* some other failure */ gLibMessages.restoreFailed(err); break; } /* indicate failure */ return nil; } /* note that we've successfully restored the game */ gLibMessages.restoreOkay(); /* set the appropriate restore-action code */ PostRestoreObject.restoreCode = code; /* notify all PostRestoreObject instances */ PostRestoreObject.classExec(); /* * look around, to refresh the player's memory of the state the * game was in when saved */ "\b"; libGlobal.playerChar.lookAround(true); /* indicate success */ return true; } /* * There's no point in including this in undo. If the command * succeeds, it's not undoable itself, and there won't be any undo * information in the newly restored state. If the command fails, it * won't make any changes to the game state, so there won't be * anything to undo. */ includeInUndo = nil ; /* * Subclass of Restore action that takes a literal string as part of the * command. The filename must be a literal enclosed in quotes, and the * string (with the quotes) must be stored in our fname_ property by * assignment of a quotedStringPhrase production in the grammar rule. */ DefineAction(RestoreString, RestoreAction) execSystemAction() { /* * Perform the restore, using the filename given in our fname_ * parameter, trimmed of quotes. Use code 2, the same as any * other explicit RESTORE command. */ performRestore(fname_.getStringText(), 2); /* abandon any additional commands on the same command line */ throw new TerminateCommandException(); } ; /* ------------------------------------------------------------------------ */ /* * Restart the game from the beginning. */ DefineSystemAction(Restart) execSystemAction() { /* confirm that they really want to restart */ gLibMessages.confirmRestart(); if (yesOrNo()) { /* * The confirmation input will have put us into * start-of-command mode for sequencing purposes; force the * sequencer back to mid-command mode, so we can show * inter-command separation before the restart. */ /* restart the game */ doRestartGame(); } else { /* confirm that we're not really restarting */ gLibMessages.notRestarting(); } } /* carry out the restart action */ doRestartGame() { /* * Show a command separator, to provide separation from any * introductory text that we'll show on restarting. Note that * we probably just asked for confirmation, which means that the * command sequencer will be in start-of-command mode; force it * back to mid-command mode so we show inter-command separation. */ commandSequencer.setCommandMode(); "<.commandsep>"; /* before restarting, notify anyone interested of our intentions */ PreRestartObject.classExec(); /* * Throw a 'restart' signal; the main entrypoint loop will catch * this and actually perform the restart. * * Note that we *could* do the VM reset (via restartGame()) here, * but there's an advantage to doing it in the main loop: we * won't be in the stack context of whatever command we're * performing. If we did the restart here, it's possible that * some useless objects would survive the VM reset just because * they're referenced from within a caller's stack frame. Those * objects would immediately go out of scope when we get back to * the main loop, but they might survive long enough to create * apparent inconsistencies. In particular, if we did a * firstObj/nextObj loop, we could discover those objects and * re-establish more lasting references to them, which we * certainly don't want to do. By deferring the VM reset until * we get back to the main loop, we'll ensure that objects won't * survive the reset just because they're on the stack * momentarily here. */ throw new RestartSignal(); } /* there's no point in including this in undo */ includeInUndo = nil ; /* ------------------------------------------------------------------------ */ /* * Undo one turn. */ DefineSystemAction(Undo) /* * "Undo" is so special that we must override the entire action * processing sequence. We do this because undoing will restore the * game state as of the previous savepoint, which would leave all * sorts of things unsynchronized in the normal action sequence. To * avoid problems, we simply leave out any other action processing * and perform the 'undo' directly. */ doAction(issuingActor, targetActor, targetActorPhrase, countsAsIssuerTurn) { /* * the player obviously knows about UNDO, so there's no need for * a tip about it */ undoTip.makeShown(); /* * don't allow this unless the player character is performing * the command directly */ if (!targetActor.isPlayerChar) { /* * tell them this command cannot be directed to another * actor, and give up */ gLibMessages.systemActionToNPC(); return; } /* perform the undo */ performUndo(true); } /* * Perform undo. Returns true if we were successful, nil if not. * * 'asCommand' indicates whether or not the undo is being performed * as an explicit command: if so, we'll save the UNDO command for use * in AGAIN. */ performUndo(asCommand) { /* try undoing to the previous savepoint */ if (undo()) { local oldActor; local oldIssuer; local oldAction; /* notify all PostUndoObject instances */ PostUndoObject.classExec(); /* set up the globals for the command */ oldActor = gActor; oldIssuer = gIssuingActor; oldAction = gAction; /* set the new globals */ gActor = gPlayerChar; gIssuingActor = gPlayerChar; gAction = self; /* make sure we reset globals on the way out */ try { /* success - mention what we did */ gLibMessages.undoOkay(libGlobal.lastActorForUndo, libGlobal.lastCommandForUndo); /* look around, to refresh the player's memory */ libGlobal.playerChar.lookAround(true); } finally { /* restore the parser globals to how we found them */ gActor = oldActor; gIssuingActor = oldIssuer; gAction = oldAction; } /* * if this was an explicit 'undo' command, save the command * to allow repeating it with 'again' */ if (asCommand) AgainAction.saveForAgain(gPlayerChar, gPlayerChar, nil, self); /* indicate success */ return true; } else { /* no more undo information available */ gLibMessages.undoFailed(); /* indicate failure */ return nil; } } /* * "undo" is not undoable - if we undo again after an undo, we undo * the next most recent command */ includeInUndo = nil ; /* ------------------------------------------------------------------------ */ /* * Save the defaults */ DefineSystemAction(SaveDefaults) execSystemAction() { /* tell SettingsItem to save all settings */ settingsUI.saveSettingsMsg(); } /* there's no point in including this in undo */ includeInUndo = nil ; /* * Restore defaults */ DefineSystemAction(RestoreDefaults) execSystemAction() { /* * Tell SettingsItem to restore all settings. This is an * explicit request, so we want SettingsItem to describe what * happened. */ settingsUI.restoreSettingsMsg(); } /* there's no point in including this in undo */ includeInUndo = nil ; /* ------------------------------------------------------------------------ */ /* * Quit the game. */ DefineSystemAction(Quit) execSystemAction() { /* confirm that they really want to quit */ gLibMessages.confirmQuit(); if (yesOrNo()) { /* carry out the termination */ terminateGame(); } else { /* show the confirmation that we're not quitting */ gLibMessages.notTerminating(); } } /* * Carry out game termination. This can be called when we wish to * end the game without asking for any additional player * confirmation. */ terminateGame() { /* acknowledge that we're quitting */ gLibMessages.okayQuitting(); /* throw a 'quitting' signal to end the game */ throw new QuittingException; } /* there's no point in including this in undo */ includeInUndo = nil ; /* * Pause the game. This stops the real-time clock until the user * presses a key. Games that don't use the real-time clock will have no * use for this. */ DefineSystemAction(Pause) execSystemAction() { local elapsed; /* * remember the current elapsed game real time - when we are * released from the pause, we'll restore this time */ elapsed = realTimeManager.getElapsedTime(); /* show our prompt */ gLibMessages.pausePrompt(); /* keep going until we're released */ waitLoop: for (;;) { /* * Wait for a key, and see what we have. Note that we * explicitly do not want to allow any real-time events to * occur, so we simply wait forever without timeout. */ switch(inputKey()) { case ' ': /* space key - end the wait */ break waitLoop; case 's': case 'S': /* mention that we're saving */ gLibMessages.pauseSaving(); /* * set the elapsed time to the time when we started, so * that the saved position reflects the time at the * start of the pause */ realTimeManager.setElapsedTime(elapsed); /* save the game - go run the normal SAVE command */ SaveAction.execSystemAction(); /* show our prompt again */ "<.p>"; gLibMessages.pausePrompt(); /* go back to wait for another key */ break; case '[eof]': /* end-of-file on keyboard input - throw an error */ "\b"; throw new EndOfFileException(); default: /* ignore other keys; just go back to wait again */ break; } } /* show the released-from-pause message */ gLibMessages.pauseEnded(); /* * set the real-time clock to the same elapsed game time * that we had when we started the pause, so that the * elapsed real time of the pause itself doesn't count * against the game elapsed time */ realTimeManager.setElapsedTime(elapsed); } ; /* * Change to VERBOSE mode. */ DefineSystemAction(Verbose) execSystemAction() { /* set the global 'verbose' mode */ gameMain.verboseMode.isOn = true; /* acknowledge it */ gLibMessages.acknowledgeVerboseMode(true); } ; /* * Change to TERSE mode. */ DefineSystemAction(Terse) execSystemAction() { /* set the global 'verbose' mode */ gameMain.verboseMode.isOn = nil; /* acknowledge it */ gLibMessages.acknowledgeVerboseMode(nil); } ; /* in case the score module isn't present */ property showScore; property showFullScore; property scoreNotify; /* * Show the current score. */ DefineSystemAction(Score) execSystemAction() { /* show the simple score */ if (libGlobal.scoreObj != nil) { /* show the score */ libGlobal.scoreObj.showScore(); /* * Mention the FULL SCORE command to the player if we haven't * already. Note that we only want to mention */ if (!mentionedFullScore) { /* explain about it */ gLibMessages.mentionFullScore; /* don't mention it again */ ScoreAction.mentionedFullScore = true; } } else gLibMessages.scoreNotPresent; } /* there's no point in including this in undo */ includeInUndo = nil /* have we mentioned the FULL SCORE command yet? */ mentionedFullScore = nil ; /* * Show the full score. */ DefineSystemAction(FullScore) execSystemAction() { /* show the full score in response to an explicit player request */ showFullScore(); /* this counts as a mention of the FULL SCORE command */ ScoreAction.mentionedFullScore = true; } /* show the full score */ showFullScore() { /* show the full score */ if (libGlobal.scoreObj != nil) libGlobal.scoreObj.showFullScore(); else gLibMessages.scoreNotPresent; } /* there's no point in including this in undo */ includeInUndo = nil ; /* * Show the NOTIFY status. */ DefineSystemAction(Notify) execSystemAction() { /* show the current notification status */ if (libGlobal.scoreObj != nil) gLibMessages.showNotifyStatus( libGlobal.scoreObj.scoreNotify.isOn); else gLibMessages.commandNotPresent; } ; /* * Turn score change notifications on. */ DefineSystemAction(NotifyOn) execSystemAction() { /* turn notifications on, and acknowledge the status */ if (libGlobal.scoreObj != nil) { libGlobal.scoreObj.scoreNotify.isOn = true; gLibMessages.acknowledgeNotifyStatus(true); } else gLibMessages.commandNotPresent; } ; /* * Turn score change notifications off. */ DefineSystemAction(NotifyOff) execSystemAction() { /* turn notifications off, and acknowledge the status */ if (libGlobal.scoreObj != nil) { libGlobal.scoreObj.scoreNotify.isOn = nil; gLibMessages.acknowledgeNotifyStatus(nil); } else gLibMessages.commandNotPresent; } ; /* * Show version information for the game and the library modules the * game is using. */ DefineSystemAction(Version) execSystemAction() { /* show the version information for each library */ foreach (local cur in ModuleID.getModuleList()) cur.showVersion(); } /* there's no point in including this in undo */ includeInUndo = nil ; /* * Show the credits for the game and the library modules the game * includes. */ DefineSystemAction(Credits) execSystemAction() { /* show the credits for each library */ foreach (local cur in ModuleID.getModuleList()) cur.showCredit(); } /* there's no point in including this in undo */ includeInUndo = nil ; /* * Show the "about" information for the game and library modules. */ DefineSystemAction(About) execSystemAction() { local anyOutput; /* watch for any output while showing module information */ anyOutput = outputManager.curOutputStream .watchForOutput(function() { /* show information for each module */ foreach (local cur in ModuleID.getModuleList()) cur.showAbout(); }); /* * if we didn't have any ABOUT information to show, display a * message to this effect */ if (!anyOutput) gLibMessages.noAboutInfo; } /* there's no point in including this in undo */ includeInUndo = nil ; /* * A state object that keeps track of our logging (scripting) status. * This is transient, because logging is controlled through the output * layer in the interpreter, which does not participate in any of the * persistence mechanisms. */ transient scriptStatus: object /* * Script file name. This is nil when logging is not in effect, and * is set to the name of the scripting file when a log file is * active. */ scriptFile = nil /* RECORD file name */ recordFile = nil /* have we warned about using NOTE without logging in effect? */ noteWithoutScriptWarning = nil ; /* * Property: object is a web temp file. The Web UI uses this to flag * that a file we're saving to is actually a temp file that will be * offered as a downloadable file to the client after the file is written * and closed. */ property isWebTempFile; /* * A base class for file-oriented actions, such as SCRIPT, RECORD, and * REPLAY. We provide common handling that prompts interactively for a * filename; subclasses must override a few methods and properties to * carry out the specific subclassed operation on the file. */ DefineSystemAction(FileOp) /* our file dialog prompt message */ filePromptMsg = '' /* the file dialog open/save type */ fileDisposition = InFileSave /* the file dialog type ID */ fileTypeID = FileTypeLog /* show our cancellation mesage */ showCancelMsg = "" /* * Carry out our file operation. * * 'desc' is an optional named argument giving a description string * entered by the user via the Save Game dialog. Some versions of * the Save Game dialog let the user enter this additional * information, which can be stored as part of the saved game * metadata. */ performFileOp(fname, ack, desc:?) { /* * Each concrete action subclass must override this to carry out * our operation. This is called when the user has successfully * selected a filename for the operation. */ } execSystemAction() { /* * ask for a file and carry out our action; since the command is * being performed directly from the command line, we want an * acknowledgment message on success */ setUpFileOp(true); } /* ask for a file, and carry out our operation is we get one */ setUpFileOp(ack) { local result; local origElapsedTime; /* note the current game time */ origElapsedTime = realTimeManager.getElapsedTime(); /* ask for a file */ result = getInputFile(filePromptMsg, fileDisposition, fileTypeID, 0); /* check the inputFile result */ switch(result[1]) { case InFileSuccess: /* carry out our file operation */ if (result.length >= 3) performFileOp(result[2], ack, desc:result[3]); else performFileOp(result[2], ack); break; case InFileFailure: /* advise of the failure of the prompt */ if (result.length() > 1) gLibMessages.filePromptFailedMsg(result[2]); else gLibMessages.filePromptFailed(); break; case InFileCancel: /* acknowledge the cancellation */ showCancelMsg(); break; } /* * restore the original elapsed game time, so that the time spent * in the file selector dialog doesn't count against the game * time */ realTimeManager.setElapsedTime(origElapsedTime); } /* we can't include this in undo, as it affects external files */ includeInUndo = nil /* don't allow repeating with AGAIN */ isRepeatable = nil ; /* * Turn scripting on. This creates a text file that contains a * transcript of all commands and responses from this point forward. */ DefineAction(Script, FileOpAction) /* our file dialog parameters - ask for a log file to save */ filePromptMsg = (gLibMessages.getScriptingPrompt()) fileTypeID = FileTypeLog fileDisposition = InFileSave /* show our cancellation mesasge */ showCancelMsg() { gLibMessages.scriptingCanceled(); } /* * set up scripting - this can be used to set up scripting * programmatically, in the course of carrying out another action */ setUpScripting(ack) { setUpFileOp(ack); } /* turn on scripting to the given file */ performFileOp(fname, ack) { /* turn on logging */ local ok = nil, exc = nil; try { ok = aioSetLogFile(fname, LogTypeTranscript); } catch (Exception e) { exc = e; } if (ok) { /* remember that scripting is in effect */ scriptStatus.scriptFile = fname; /* * forget any past warning that we've issued about NOTE * without a script in effect; the next time scripting isn't * active, we'll want to issue a new warning, since they * might not be aware at that point that the scripting we're * starting now has ended */ scriptStatus.noteWithoutScriptWarning = nil; /* note that logging is active, if acknowledgment is desired */ if (ack) { if (fname.isWebTempFile) gLibMessages.scriptingOkayWebTemp(); else gLibMessages.scriptingOkay(); } } else { /* scripting is no longer in effect */ scriptStatus.scriptFile = nil; /* show an error, if acknowledgment is desired */ if (ack) { if (exc != nil) gLibMessages.scriptingFailedException(exc); else gLibMessages.scriptingFailed; } } } ; /* * Subclass of Script action taking a quoted string as part of the * command syntax. The grammar rule must set our fname_ property to a * quotedStringPhrase subproduction. */ DefineAction(ScriptString, ScriptAction) execSystemAction() { /* if there's a filename, we don't need to prompt */ if (fname_ != nil) { /* set up scripting to the filename specified in the command */ performFileOp(fname_.getStringText(), true); } else { /* there's no filename, so prompt as usual */ inherited(); } } ; /* * Turn scripting off. This stops recording the game transcript started * with the most recent SCRIPT command. */ DefineSystemAction(ScriptOff) execSystemAction() { /* turn off scripting */ turnOffScripting(true); } /* turn off scripting */ turnOffScripting(ack) { /* if we're not in a script file, ignore it */ if (scriptStatus.scriptFile == nil) { gLibMessages.scriptOffIgnored(); return; } /* cancel scripting in the interpreter's output layer */ aioSetLogFile(nil, LogTypeTranscript); /* remember that scripting is no longer in effect */ scriptStatus.scriptFile = nil; /* acknowledge the change, if desired */ if (ack) gLibMessages.scriptOffOkay(); } /* we can't include this in undo, as it affects external files */ includeInUndo = nil ; /* * RECORD - this is similar to SCRIPT, but stores a file containing only * the command input, not the output. */ DefineAction(Record, FileOpAction) /* our file dialog parameters - ask for a log file to save */ filePromptMsg = (gLibMessages.getRecordingPrompt()) fileTypeID = FileTypeCmd fileDisposition = InFileSave /* show our cancellation mesasge */ showCancelMsg() { gLibMessages.recordingCanceled(); } /* * set up recording - this can be used to set up scripting * programmatically, in the course of carrying out another action */ setUpRecording(ack) { setUpFileOp(ack); } /* turn on recording to the given file */ performFileOp(fname, ack) { /* turn on command logging */ local ok = nil, exc = nil; try { ok = aioSetLogFile(fname, logFileType); } catch (Exception e) { exc = e; } if (ok) { /* remember that recording is in effect */ scriptStatus.recordFile = fname; /* note that logging is active, if acknowledgment is desired */ if (ack) gLibMessages.recordingOkay(); } else { /* recording failed */ scriptStatus.recordFile = nil; /* show an error if acknowledgment is desired */ if (ack) { if (exc != nil) gLibMessages.recordingFailedException(exc); else gLibMessages.recordingFailed(); } } } /* the log file type - by default, we open a regular command log */ logFileType = LogTypeCommand ; /* subclass of Record action that sets up an event script recording */ DefineAction(RecordEvents, RecordAction) logFileType = LogTypeScript ; /* subclass of Record action taking a quoted string for the filename */ DefineAction(RecordString, RecordAction) execSystemAction() { /* set up scripting to the filename specified in the command */ performFileOp(fname_.getStringText(), true); } ; /* subclass of RecordString action that sets up an event script recording */ DefineAction(RecordEventsString, RecordStringAction) logFileType = LogTypeScript ; /* * Turn command recording off. This stops recording the command log * started with the most recent RECORD command. */ DefineSystemAction(RecordOff) execSystemAction() { /* turn off recording */ turnOffRecording(true); } /* turn off recording */ turnOffRecording(ack) { /* if we're not recording anything, ignore it */ if (scriptStatus.recordFile == nil) { gLibMessages.recordOffIgnored(); return; } /* cancel recording in the interpreter's output layer */ aioSetLogFile(nil, LogTypeCommand); /* remember that recording is no longer in effect */ scriptStatus.recordFile = nil; /* acknowledge the change, if desired */ if (ack) gLibMessages.recordOffOkay(); } /* we can't include this in undo, as it affects external files */ includeInUndo = nil ; /* * REPLAY - play back a command log previously recorded. */ DefineAction(Replay, FileOpAction) /* our file dialog parameters - ask for a log file to save */ filePromptMsg = (gLibMessages.getReplayPrompt()) fileTypeID = FileTypeCmd fileDisposition = InFileOpen /* show our cancellation mesasge */ showCancelMsg() { gLibMessages.replayCanceled(); } /* script flags passed to setScriptFile */ scriptOptionFlags = 0 /* replay the given file */ performFileOp(fname, ack) { /* * Note that we're reading from the script file if desired. Do * this before opening the script, so that we display the * acknowledgment even if we're in 'quiet' mode. */ if (ack) gLibMessages.inputScriptOkay( fname.ofKind(TemporaryFile) ? fname.getFilename() : fname); /* activate the script file */ local ok = nil, exc = nil; try { ok = setScriptFile(fname, scriptOptionFlags); } catch (Exception e) { exc = e; } if (!ok) { if (exc != nil) gLibMessages.inputScriptFailed(exc); else gLibMessages.inputScriptFailed(); } } ; /* subclass of Replay action taking a quoted string for the filename */ DefineAction(ReplayString, ReplayAction) execSystemAction() { /* * if there's a string, use the string as the filename; * otherwise, inherit the default handling to ask for a filename */ if (fname_ != nil) { /* set up scripting to the filename specified in the command */ performFileOp(fname_.getStringText(), true); } else { /* inherit the default handling to ask for a filename */ inherited(); } } ; /* in case the footnote module is not present */ property showFootnote; /* * Footnote - this requires a numeric argument parsed via the * numberPhrase production and assigned to the numMatch property. */ DefineSystemAction(Footnote) execSystemAction() { /* ask the Footnote class to do the work */ if (libGlobal.footnoteClass != nil) libGlobal.footnoteClass.showFootnote(numMatch.getval()); else gLibMessages.commandNotPresent; } /* there's no point in including this in undo */ includeInUndo = nil ; property footnoteSettings; /* base class for FOOTNOTES xxx commands */ DefineSystemAction(Footnotes) execSystemAction() { if (libGlobal.footnoteClass != nil) { /* set my footnote status in the global setting */ libGlobal.footnoteClass.footnoteSettings.showFootnotes = showFootnotes; /* acknowledge it */ gLibMessages.acknowledgeFootnoteStatus(showFootnotes); } else gLibMessages.commandNotPresent; } /* * the footnote status I set when this command is activated - this * must be overridden by each subclass */ showFootnotes = nil ; DefineAction(FootnotesFull, FootnotesAction) showFootnotes = FootnotesFull ; DefineAction(FootnotesMedium, FootnotesAction) showFootnotes = FootnotesMedium ; DefineAction(FootnotesOff, FootnotesAction) showFootnotes = FootnotesOff ; DefineSystemAction(FootnotesStatus) execSystemAction() { /* show the current status */ if (libGlobal.footnoteClass != nil) gLibMessages.showFootnoteStatus(libGlobal.footnoteClass. footnoteSettings.showFootnotes); else gLibMessages.commandNotPresent; } /* there's no point in including this in undo */ includeInUndo = nil ; DefineIAction(Inventory) execAction() { /* show the actor's inventory in the current mode */ gActor.showInventory(inventoryMode == InventoryTall); } /* current inventory mode - start in 'wide' mode by default */ inventoryMode = InventoryWide; ; DefineIAction(InventoryTall) execAction() { /* set inventory mode to 'tall' */ InventoryAction.inventoryMode = InventoryTall; /* run the inventory action */ InventoryAction.checkAction(); InventoryAction.execAction(); } ; DefineIAction(InventoryWide) execAction() { /* set inventory mode to 'wide' */ InventoryAction.inventoryMode = InventoryWide; /* run the inventory action */ InventoryAction.checkAction(); InventoryAction.execAction(); } ; DefineIAction(Wait) execAction() { /* just show the "time passes" message */ defaultReport(&timePassesMsg); } ; DefineIAction(Look) execAction() { /* show the actor's current location in verbose mode */ gActor.lookAround(true); } ; DefineIAction(Sleep) execAction() { /* let the actor handle it */ gActor.goToSleep(); } ; DefineTAction(Take) /* this is a basic inventory-management verb, so allow ALL with it */ actionAllowsAll = true /* get the ALL list for the direct object */ getAllDobj(actor, scopeList) { local locList; local dropLoc; local actorLoc; /* * Include all of the objects that are directly in the actor's * immediate container, the container's container, and so on out * to the "drop destination" location (which is where things go * when we DROP them, and is meant to represent the nearest * platform-like or floor-like container). Also include anything * that's directly in anything fixed in place within one of these * containers. Don't include anything that actually contains the * actor, since we normally can't pick up something we're inside. * * Start by getting the actor's immediate location and drop * destination location. */ actorLoc = actor.location; dropLoc = actor.getDropDestination(nil, nil); /* * create a vector to hold the location list, and start it off * with the drop location */ locList = new Vector(10); locList.append(dropLoc); /* now work up the location list until we hit the drop location */ for (local cur = actorLoc ; cur != nil && cur != dropLoc ; cur = cur.location) { /* add this container to the list */ locList.append(cur); } /* * now generate the subset of in-scope objects that are directly * in any of these locations (or directly in items fixed in place * within any of these locations), and return the result */ return scopeList.subset( {x: (locList.indexWhich( {loc: x.isDirectlyIn(loc) || x.isInFixedIn(loc)}) != nil && !actor.isIn(x)) }); } ; DefineTIAction(TakeFrom) /* this is a basic inventory-management verb, so allow ALL with it */ actionAllowsAll = true /* get the ALL list for the direct object */ getAllDobj(actor, scopeList) { /* ask the indirect object for the list */ return getIobj() == nil ? [] : getIobj().getAllForTakeFrom(scopeList); } ; DefineTAction(Remove) ; DefineTAction(Drop) /* this is a basic inventory-management verb, so allow ALL with it */ actionAllowsAll = true /* get the ALL list for the direct object */ getAllDobj(actor, scopeList) { /* include only objects directly held by the actor */ return scopeList.subset({x: x.isDirectlyIn(actor)}); } ; DefineTAction(Examine) ; DefineTAction(Read) ; DefineTAction(LookIn) ; DefineTAction(Search) ; DefineTAction(LookUnder) ; DefineTAction(LookBehind) ; DefineTAction(LookThrough) ; DefineTAction(Feel) ; DefineTAction(Taste) ; DefineTAction(Smell) ; DefineTAction(ListenTo) ; /* * Base class for undirected sensing, such as "listen" or "smell" with no * object. We'll scan for things that have a presence in the * corresponding sense and describe each one. */ DefineIAction(SenseImplicit) /* the sense in which I operate */ mySense = nil /* the object property to display this sense's description */ descProp = nil /* the default message to display if we find nothing specific to sense */ defaultMsgProp = nil /* the Lister we use to show the items */ resultLister = nil /* execute the action */ execAction() { local senseTab; local presenceList; /* get a list of everything in range of this sense for the actor */ senseTab = gActor.senseInfoTable(mySense); /* get a list of everything with a presence in this sense */ presenceList = senseInfoTableSubset(senseTab, {obj, info: obj.(mySense.presenceProp)}); /* * if there's anything in the list, show it; otherwise, show a * default report */ if (presenceList.length() != 0) { /* show the list using our lister */ resultLister.showList(gActor, nil, presenceList, 0, 0, senseTab, nil); } else { /* there's nothing to show - say so */ defaultReport(defaultMsgProp); } } ; DefineAction(SmellImplicit, SenseImplicitAction) mySense = smell descProp = &smellDesc defaultMsgProp = ¬hingToSmellMsg resultLister = smellActionLister ; DefineAction(ListenImplicit, SenseImplicitAction) mySense = sound descProp = &soundDesc defaultMsgProp = ¬hingToHearMsg resultLister = listenActionLister ; DefineTIAction(PutIn) /* this is a basic inventory-management verb, so allow ALL with it */ actionAllowsAll = true /* get the ALL list for the direct object */ getAllDobj(actor, scopeList) { local loc; local iobj = nil; local iobjIdent = nil; /* get the actor's location */ loc = actor.location; /* if we have an iobj list, retrieve its first element */ if (iobjList_ != nil && iobjList_.length() > 0) { iobj = iobjList_[1].obj_; iobjIdent = iobj.getIdentityObject(); } /* * Include objects that are directly in the actor's location, or * within fixed items in the actor's location, or directly in the * actor's inventory. * * Exclude the indirect object and its "identity" object (since * we obviously can't put the indirect object in itself), and * exclude everything already directly in the indirect object. */ return scopeList.subset({x: (x.isDirectlyIn(loc) || x.isInFixedIn(loc) || x.isDirectlyIn(actor)) && x != iobj && x != iobjIdent && !x.isDirectlyIn(iobj)}); } ; DefineTIAction(PutOn) /* this is a basic inventory-management verb, so allow ALL with it */ actionAllowsAll = true /* get the ALL list for the direct object */ getAllDobj(actor, scopeList) { /* use the same strategy that we do in PutIn */ local loc = actor.location; return scopeList.subset({x: (x.isDirectlyIn(loc) || x.isInFixedIn(loc) || x.isDirectlyIn(actor)) && x != getIobj() && !x.isDirectlyIn(getIobj())}); } ; DefineTIAction(PutUnder) ; DefineTIAction(PutBehind) ; DefineTAction(Wear) ; DefineTAction(Doff) ; DefineConvTopicTAction(AskFor, IndirectObject) ; DefineConvTopicTAction(AskAbout, IndirectObject) ; DefineConvTopicTAction(TellAbout, IndirectObject) /* * TELL ABOUT is a conversational address, as opposed to an order, * if the direct object of the action is the same as the issuer: in * this case, the command has the form <actor>, TELL ME ABOUT * <topic>, which has exactly the same meaning as ASK <actor> ABOUT * <topic>. */ isConversational(issuer) { local dobj; /* * if the resolved direct object matches the issuer, it's * conversational */ dobj = getResolvedDobjList(); return (dobj.length() == 1 && dobj[1] == issuer); } ; /* * AskVague and TellVague are for syntactically incorrect phrasings that * a player might accidentally type, especially in conjunction with a * past SpecialTopic prompt; in English, for example, we define these as * ASK <actor> <topic> and TELL <actor> <topic>. These are used only to * provide more helpful error messages. */ DefineTopicTAction(AskVague, IndirectObject) ; DefineTopicTAction(TellVague, IndirectObject) ; DefineConvIAction(Hello) execAction() { /* the issuing actor is saying hello to the target actor */ gIssuingActor.sayHello(gActor); } ; DefineConvIAction(Goodbye) execAction() { /* the issuing actor is saying goodbye to the target actor */ gIssuingActor.sayGoodbye(gActor); } ; DefineConvIAction(Yes) execAction() { /* the issuing actor is saying yes to the target actor */ gIssuingActor.sayYes(gActor); } ; DefineConvIAction(No) execAction() { /* the issuing actor is saying no to the target actor */ gIssuingActor.sayNo(gActor); } ; /* * Invoke the active SpecialTopic. This isn't a real command - the * player will never actually type this; rather, it's a pseudo-command * that we send to ourselves from a string pre-parser when we recognize * input that matches a SpecialTopic's custom command syntax. * * Note that we actually define the syntax for this command right here * in the language-independent library, because this isn't a real * command. The user never needs to type this command, since it's * something we generate internally. The only important language issue * is that we use a command keyword that no language will ever want to * use for a real command, so we intentionally use some near-English * gibberish. */ DefineLiteralAction(SpecialTopic) execAction() { /* * the issuing actor is saying the current special topic to the * actor's current interlocutor */ gIssuingActor.saySpecialTopic(); } /* * Repeat the action, for an AGAIN command. We need to make sure * the special text interpretation we gave to the command still * holds; if not, reparse the original text and try that. */ repeatAction(lastTargetActor, lastTargetActorPhrase, lastIssuingActor, countsAsIssuerTurn) { local cmd; /* get the original text the player entered */ cmd = getEnteredText(); /* * try running this through the special topic pre-parser again, * to see if it still has the special meaning */ if (specialTopicPreParser.doParsing(cmd, rmcCommand) .startsWith('xspcltopic ')) { /* * it still has the special meaning, so simply execute as we * normally would, by inheriting the standard Action * handling */ inherited(lastTargetActor, lastTargetActorPhrase, lastIssuingActor, countsAsIssuerTurn); } else { /* * The command no longer has the special meaning it did on * the last command, so we can't repeat this command. */ gLibMessages.againNotPossible(lastIssuingActor); } } /* * Get the original player-entered text. This is our literal * phrase, with the embedded-quote encoding decoded. */ getEnteredText() { return decodeOrig(getLiteral()); } /* * encode the original text for our literal phrase: turn double * quotes into '%q' sequences, and turn percent signs into '%%' * sequences */ encodeOrig(txt) { /* replace '%' with '%%', and double quotes with '%q' */ return txt.findReplace(['%', '"'], ['%%', '%q']); } /* decode our encoding */ decodeOrig(txt) { /* replace '%%' with '%', and '%q' with '"' */ return txt.findReplace(['%%', '%q'], ['%', '"']); } ; grammar predicate(SpecialTopic): 'xspcltopic' literalPhrase->literalMatch : SpecialTopicAction /* * Use the text of the command as originally typed by the player as * our apparent original text. */ getOrigText() { return getEnteredText(); } ; /* in case they try typing just 'xspcltopic' */ grammar predicate(EmptySpecialTopic): 'xspcltopic' : IAction /* just act like we don't understand this command */ execAction() { gLibMessages.commandNotPresent; } ; DefineTAction(Kiss) ; DefineIAction(Yell) execAction() { /* yelling generally has no effect; issue a default response */ mainReport(&okayYellMsg); } ; DefineTAction(TalkTo) ; DefineSystemAction(Topics) execSystemAction() { /* check to see if any suggestions are defined in the entire game */ if (firstObj(SuggestedTopic, ObjInstances) != nil) { /* we have topics - let the actor handle it */ gActor.suggestTopics(true); } else { /* there are no topics at all, so this command isn't used */ gLibMessages.commandNotPresent; } } /* don't include this in undo */ includeInUndo = nil ; DefineTIAction(GiveTo) getDefaultIobj(np, resolver) { /* check the actor for a current interlocutor */ local obj = resolver.getTargetActor().getCurrentInterlocutor(); if (obj != nil) return [new ResolveInfo(obj, 0, np)]; else return inherited(np, resolver); } ; /* * Define a global remapping to transform commands of the form "X, GIVE * ME Y" to the format "ME, ASK X FOR Y". This makes it easier to write * the code to handle these sorts of exchanges, since it means you only * have to write it in the ASK FOR handler. */ giveMeToAskFor: GlobalRemapping /* * Remap a command, if applicable. We look for commands of the form * "X, GIVE ME Y": we look for a GiveTo action whose indirect object * is the same as the issuing actor. When we find this form of * command, we rewrite it to "ME, ASK X FOR Y". */ getRemapping(issuingActor, targetActor, action) { /* * if it's of the form "X, GIVE Y TO Z", where Z is the issuing * actor (generally ME, but it could conceivably be someone * else), transform it into "Z, ASK X FOR Y". */ if (action.ofKind(GiveToAction) && action.canIobjResolveTo(issuingActor)) { /* create the ASK FOR action */ local newAction = AskForAction.createActionInstance(); /* remember the original version of the action */ newAction.setOriginalAction(action); /* * Changing the phrasing from "X, GIVE Y TO Z" to "Z, ASK X * FOR Y" will change the target actor from X in the old * version to Z in the new version. In the original format, * the pronouns "you", "your", and "yours" implicitly refers * to Z ("Bob, give me your book" implies "bob's book"). The * rewrite will change that, though - assuming that Z is a * second-person actor, "you" will by default refer to Z in * the rewrite. In order to preserve the original meaning, * we have to override "you" in the rewrite so that it * continues to refer to "X", which we can do using a pronoun * override in the new action. */ newAction.setPronounOverride(PronounYou, targetActor); /* * The direct object - the person we're asking - is the * original target actor ("bob" in "bob, give me x"). Since * this is a specific object, we need to wrap it in a * PreResolvedProd. */ local dobj = new PreResolvedProd(targetActor); /* * The thing we're asking for is the original direct object. * ASK FOR takes a topic phrase for its indirect object, * whereas GIVE TO takes a regular noun phrase. The two * aren't quite identical syntactically, so we'll do better * if we re-parse the original dobj noun phrase as a topic * phrase. Fortunately, this is easy... */ local iobj = newAction.reparseMatchAsTopic( action.dobjMatch, issuingActor, issuingActor); /* set the object match trees */ newAction.setObjectMatches(dobj, iobj); /* * Return the new command, addressing the *issuing* actor * this time around. */ return [issuingActor, newAction]; } /* it's not ours */ return nil; } ; DefineTIAction(ShowTo) getDefaultIobj(np, resolver) { /* check the actor for a current interlocutor */ local obj = resolver.getTargetActor().getCurrentInterlocutor(); if (obj != nil) return [new ResolveInfo(obj, 0, np)]; else return inherited(np, resolver); } ; DefineTAction(Follow) /* * For resolving our direct object, we want to include in the scope * any item that isn't present but which the actor saw departing the * present location. */ initResolver(issuingActor, targetActor) { /* inherit the base resolver initialization */ inherited(issuingActor, targetActor); /* * add to the scope all of the actor's followable objects - * these are the objects which the actor has witnessed leaving * the actor's present location */ scope_ = scope_.appendUnique(targetActor.getFollowables()); } ; DefineTAction(Attack) ; DefineTIAction(AttackWith) /* * for the indirect object, limit 'all' and defaults to the items in * inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineTAction(Throw) ; DefineTAction(ThrowDir) /* get the direction of the throwing (as a Direction object) */ getDirection() { return dirMatch.dir; } ; DefineTIAction(ThrowAt) ; DefineTIAction(ThrowTo) ; DefineTAction(Dig) ; DefineTIAction(DigWith) /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineIAction(Jump) preCond = [actorStanding] execAction() { /* show the default report for jumping in place */ mainReport(&okayJumpMsg); } ; DefineTAction(JumpOver) ; DefineTAction(JumpOff) ; DefineIAction(JumpOffI) execAction() { mainReport(&cannotJumpOffHereMsg); } ; DefineTAction(Push) ; DefineTAction(Pull) ; DefineTAction(Move) ; DefineTIAction(MoveWith) /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineTIAction(MoveTo) ; DefineTAction(Turn) ; DefineTIAction(TurnWith) /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineLiteralTAction(TurnTo, IndirectObject) ; DefineTAction(Set) ; DefineLiteralTAction(SetTo, IndirectObject) ; DefineTAction(TypeOn) ; DefineLiteralTAction(TypeLiteralOn, DirectObject) ; DefineLiteralTAction(EnterOn, DirectObject) ; DefineTAction(Consult) ; DefineTopicTAction(ConsultAbout, IndirectObject) getDefaultDobj(np, resolver) { /* * if the actor has consulted something before, and that object * is still visible, use it as the default for this consultation */ local actor = resolver.getTargetActor(); local obj = actor.lastConsulted; if (obj != nil && actor.canSee(obj)) return [new ResolveInfo(obj, DefaultObject, np)]; else return inherited(np, resolver); } /* * Filter the topic phrase resolution. If we know our direct object, * and it's a Consultable, refer the resolution to the Consultable. */ filterTopic(lst, np, resolver) { local dobj; /* check the direct object */ if (dobjList_ != nil && dobjList_.length() == 1 && (dobj = dobjList_[1].obj_).ofKind(Consultable)) { /* * we have a Consultable direct object - let it handle the * topic phrase resolution */ return dobj.resolveConsultTopic(lst, topicMatch, resolver); } else { /* otherwise, use the default handling */ return inherited(lst, np, resolver); } } ; DefineTAction(Switch) ; DefineTAction(Flip) ; DefineTAction(TurnOn) ; DefineTAction(TurnOff) ; DefineTAction(Light) ; DefineTAction(Burn) ; DefineTIAction(BurnWith) /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } /* resolve the direct object first */ resolveFirst = DirectObject ; DefineTAction(Extinguish) ; DefineTIAction(AttachTo) ; DefineTIAction(DetachFrom) ; DefineTAction(Detach) ; DefineTAction(Break) ; DefineTAction(Cut) ; DefineTIAction(CutWith) ; DefineTAction(Climb) ; DefineTAction(ClimbUp) ; DefineTAction(ClimbDown) ; DefineTAction(Open) ; DefineTAction(Close) ; DefineTAction(Lock) ; DefineTAction(Unlock) ; DefineTIAction(LockWith) /* * Resolve the direct object (the lock) first, so that we know what * we're trying to unlock when we're verifying the key. This allows * us to (optionally) boost the likelihood of a known good key for * disambiguation. */ resolveFirst = DirectObject /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineTIAction(UnlockWith) /* resolve the direct object first, for the same reason as in LockWith */ resolveFirst = DirectObject /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineTAction(Eat) ; DefineTAction(Drink) ; DefineTAction(Pour) ; DefineTIAction(PourInto) ; DefineTIAction(PourOnto) ; DefineTAction(Clean) ; DefineTIAction(CleanWith) /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineIAction(Sit) execAction() { /* * if the actor is already sitting, just say so; otherwise, ask * what they want to sit on */ if (gActor.posture == sitting) reportFailure(&alreadySittingMsg); else askForDobj(SitOn); } ; DefineTAction(SitOn) ; DefineIAction(Lie) execAction() { /* * if the actor is already lying down, just say so; otherwise, * ask what they want to lie on */ if (gActor.posture == lying) reportFailure(&alreadyLyingMsg); else askForDobj(LieOn); } ; DefineTAction(LieOn) ; DefineTAction(StandOn) ; DefineIAction(Stand) execAction() { /* let the actor handle it */ gActor.standUp(); } ; DefineTAction(Board) ; DefineTAction(GetOutOf) getAllDobj(actor, scopeList) { /* 'all' for 'get out of' is the actor's immediate container */ return scopeList.subset({x: actor.isDirectlyIn(x)}); } ; DefineTAction(GetOffOf) getAllDobj(actor, scopeList) { /* 'all' for 'get off of' is the actor's immediate container */ return scopeList.subset({x: actor.isDirectlyIn(x)}); } ; DefineIAction(GetOut) execAction() { /* let the actor handle it */ gActor.disembark(); } ; DefineTAction(Fasten) ; DefineTIAction(FastenTo) ; DefineTAction(Unfasten) ; DefineTIAction(UnfastenFrom) ; DefineTAction(PlugIn) ; DefineTIAction(PlugInto) ; DefineTAction(Unplug) ; DefineTIAction(UnplugFrom) ; DefineTAction(Screw) ; DefineTIAction(ScrewWith) /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; DefineTAction(Unscrew) ; DefineTIAction(UnscrewWith) /* limit 'all' for the indirect object to items in inventory */ getAllIobj(actor, scopeList) { return scopeList.subset({x: x.isIn(actor)}); } ; /* ------------------------------------------------------------------------ */ /* * Travel Action - this is the base class for verbs that attempt to move * an actor to a new location via one of the directional connections * from the current location. * * Each grammar rule for this action must set the 'dirMatch' property to * a DirectionProd match object that gives the direction. */ DefineIAction(Travel) execAction() { local conn; /* * Perform the travel via the connector, if we have one. If * there's no connector defined for this direction, show a * default "you can't go that way" message. */ if ((conn = getConnector()) != nil) { /* * we have a connector - use the pseudo-action TravelVia with * the connector to carry out the travel */ replaceAction(TravelVia, conn); } else { /* no connector - show a default "can't go that way" error */ mainReport(&cannotGoThatWayMsg); } } /* get the direction object for the travel */ getDirection() { return dirMatch != nil ? dirMatch.dir : nil; } /* * Get my travel connector. My connector is given by the travel * link property for this action as defined in the actor's current * location. */ getConnector() { /* ask the location for the connector in my direction */ return gActor.location == nil ? nil : gActor.location.getTravelConnector(getDirection(), gActor); } /* * The grammar rules for the individual directions will usually just * create a base TravelAction object, rather than one of the * direction-specific subclasses (NorthAction, etc). For * convenience in testing the action, though, treat ourself as * matching the subclass with the same direction. */ actionOfKind(cls) { /* * If they're asking about a specific-direction TravelAction * subclass, then we match it if our own direction matches that * of the given subclass, and we fail to match if our direction * doesn't match the given direction. */ if (cls.ofKind(TravelAction) && cls.getDirection() != nil) { /* we match if and only if the direction matches */ return (getDirection() == cls.getDirection()); } /* otherwise, inherit the default handling */ return inherited(cls); } ; /* for a vague command such as GO, which doesn't say where to go */ DefineIAction(VagueTravel) execAction() { /* simply ask for a direction */ reportFailure(&whereToGoMsg); } ; /* * This class makes it convenient to synthesize a TravelAction given a * Direction object. To create a travel action for a direction, use * *. new TravelDirAction(direction) * * where 'direction' is the direction object (northDirection, etc) for * the desired direction of travel. Note that if you want to use the * resulting object in replaceAction() or one of the similar macros, * you'll need to go directly to the underlying function rather than * using the standard macro, since the macros expect a literal action * name rather than an object. For example: * *. _replaceAction(gActor, new TravelDirAction(getDirection)); */ DefineAction(TravelDir, TravelAction) construct(dir) { /* remember my direction */ dir_ = dir; } /* get my direction */ getDirection() { return dir_; } /* my direction, normally specified during construction */ dir_ = nil ; /* * To make it more convenient to use directional travel actions as * synthesized commands, define a set of action classes for the specific * directions. */ DefineAction(North, TravelAction) getDirection = northDirection ; DefineAction(South, TravelAction) getDirection = southDirection ; DefineAction(East, TravelAction) getDirection = eastDirection ; DefineAction(West, TravelAction) getDirection = westDirection ; DefineAction(Northeast, TravelAction) getDirection = northeastDirection ; DefineAction(Northwest, TravelAction) getDirection = northwestDirection ; DefineAction(Southeast, TravelAction) getDirection = southeastDirection ; DefineAction(Southwest, TravelAction) getDirection = southwestDirection ; DefineAction(In, TravelAction) getDirection = inDirection ; DefineAction(Out, TravelAction) getDirection = outDirection ; DefineAction(Up, TravelAction) getDirection = upDirection ; DefineAction(Down, TravelAction) getDirection = downDirection ; DefineAction(Fore, TravelAction) getDirection = foreDirection ; DefineAction(Aft, TravelAction) getDirection = aftDirection ; DefineAction(Port, TravelAction) getDirection = portDirection ; DefineAction(Starboard, TravelAction) getDirection = starboardDirection ; /* * Non-directional travel actions */ DefineTAction(GoThrough) ; DefineTAction(Enter) ; /* * An internal action for traveling via a connector. This isn't a real * action, and shouldn't have a grammar defined for it. The purpose of * this action is to allow real actions that cause travel via a * connector to be implemented by mapping to this internal action, which * we implement on the base travel connector class. */ DefineTAction(TravelVia) /* * The direct object of this synthetic action isn't necessarily an * ordinary simulation object: it could be a TravelConnector instead. * Since callers asking for a direct object almost always expect a * simulation object, returning a non-simulation object here can be * problematic. To avoid this, we return an empty object list by * default - this ensures that no one who asks for the direct object * of the verb will get back a non-simulation travel connector. */ getCurrentObjects = [] ; /* "go back" */ DefineIAction(GoBack) execAction() { /* ask the actor to handle it */ gActor.reverseLastTravel(); } ; /* ------------------------------------------------------------------------ */ /* * Combined pushing-and-traveling action ("push crate north", "drag sled * into cave"). All of these are based on a base action class, which * defines the methods invoked on the object being pushed; the * subclasses provide a definition of the connector that determines * where the travel takes us. */ DefineTAction(PushTravel) /* * Carry out the nested travel action for the special combination * push-traveler. This should carry out the same action we would * have performed for the underlying basic travel. * * This method is invoked by the TravelPushable to carry out a * push-travel action. The TravelPushable object will first set up * a PushTraveler as the actor's global traveler, and it will then * invoke this method to carry out the actual travel with that * special traveler in effect. Our job is to provide the mapping to * the correct underlying simple travel action; since we'll be * moving the PushTraveler object, we can move it using the ordinary * non-push travel action as though it were any other traveler. * * This method is abstract - each subclass must define it * appropriately. */ // performTravel() { } ; /* * For directional push-and-travel commands, we define a common base * class that does the work to find the connector based on the room's * directional connector. * * Subclasses for grammar rules must define the 'dirMatch' property to * be a DirectionProd object for the associated direction. */ DefineAction(PushTravelDir, PushTravelAction) /* * Get the direction we're going. By default, we return the * direction associated with the dirMatch match object from our * grammar match. */ getDirection() { return dirMatch.dir; } /* carry out the nested travel action for a PushTravel */ performTravel() { local conn; /* ask the actor's location for the connector in our direction */ conn = gActor.location.getTravelConnector(getDirection(), gActor); /* perform a nested TravelVia on the connector */ nestedAction(TravelVia, conn); } ; /* * To make it easy to synthesize actions for pushing objects, define * individual subclasses for the various directions. */ DefineAction(PushNorth, PushTravelDirAction) getDirection = northDirection ; DefineAction(PushSouth, PushTravelDirAction) getDirection = southDirection ; DefineAction(PushEast, PushTravelDirAction) getDirection = eastDirection ; DefineAction(PushWest, PushTravelDirAction) getDirection = westDirection ; DefineAction(PushNorthwest, PushTravelDirAction) getDirection = northwestDirection ; DefineAction(PushNortheast, PushTravelDirAction) getDirection = northeastDirection ; DefineAction(PushSouthwest, PushTravelDirAction) getDirection = southwestDirection ; DefineAction(PushSoutheast, PushTravelDirAction) getDirection = southeastDirection ; DefineAction(PushUp, PushTravelDirAction) getDirection = upDirection ; DefineAction(PushDown, PushTravelDirAction) getDirection = downDirection ; DefineAction(PushIn, PushTravelDirAction) getDirection = inDirection ; DefineAction(PushOut, PushTravelDirAction) getDirection = outDirection ; DefineAction(PushFore, PushTravelDirAction) getDirection = foreDirection ; DefineAction(PushAft, PushTravelDirAction) getDirection = aftDirection ; DefineAction(PushPort, PushTravelDirAction) getDirection = portDirection ; DefineAction(PushStarboard, PushTravelDirAction) getDirection = starboardDirection ; /* * Base class for two-object push-travel commands, such as "push boulder * out of cave" or "drag sled up hill". For all of these, the connector * is given by the indirect object. */ DefineAction(PushTravelViaIobj, TIAction, PushTravelAction) /* * Verify the indirect object of the push-travel action. We'll * remap this to given corresponding simple travel action, and call * that action's verifier. */ verifyPushTravelIobj(obj, action) { /* handle this by remapping it to the underlying simple action */ remapVerify(IndirectObject, gVerifyResults, [action, obj]); } ; DefineTIActionSub(PushTravelThrough, PushTravelViaIobjAction) /* * Carry out the underlying simple travel action. This simply * performs a GoThrough on my indirect object, as though we had * typed simply GO THROUGH iobj. The PushTraveler will already be * set up as the actor's special traveler, so the ordinary GO * THROUGH command will move the special PushTraveler object as * though it were the original actor. */ performTravel() { nestedAction(GoThrough, getIobj()); } ; DefineTIActionSub(PushTravelEnter, PushTravelViaIobjAction) /* carry out the underlying simple travel as an ENTER action */ performTravel() { nestedAction(Enter, getIobj()); } ; DefineTIActionSub(PushTravelGetOutOf, PushTravelViaIobjAction) /* carry out the underlying simple travel as a GET OUT OF action */ performTravel() { nestedAction(GetOutOf, getIobj()); } ; DefineTIActionSub(PushTravelClimbUp, PushTravelViaIobjAction) /* carry out the underlying simple travel as a CLIMB UP action */ performTravel() { nestedAction(ClimbUp, getIobj()); } ; DefineTIActionSub(PushTravelClimbDown, PushTravelViaIobjAction) /* carry out the underlying simple travel as an CLIMB DOWN action */ performTravel() { nestedAction(ClimbDown, getIobj()); } ; /* * The "exits" verb. This verb explicitly shows all of the exits from * the current location. */ DefineIAction(Exits) execAction() { /* * if we have an exit lister object, invoke it; otherwise, * explain that this command isn't supported in this game */ if (gExitLister != nil) gExitLister.showExitsCommand(); else gLibMessages.commandNotPresent; } ; /* in case the exits module isn't included */ property showExitsCommand, exitsOnOffCommand; /* * Change the exit display mode. The grammar must set one of the mode * token properties to a non-nil value, according to which mode the * player selected: on_ for turning on statusline and description lists; * stat_ for turning on only the statusline list; look_ for turning on * only the room description list; and off_ for turning off everything. */ DefineSystemAction(ExitsMode) execSystemAction() { local stat, look; /* * If it's EXITS ON, turn on both statusline and room description * lists. If it's EXITS LOOK or EXITS STATUS, just turn on one * or the other. Otherwise, turn both off. */ stat = (stat_ != nil || on_ != nil); look = (look_ != nil || on_ != nil); /* update the exit display */ if (gExitLister != nil) gExitLister.exitsOnOffCommand(stat, look); else gLibMessages.commandNotPresent; } ; /* * Dummy OOPS action for times when OOPS isn't in context. We'll simply * explain how OOPS works, and that you can't use it right now. */ DefineLiteralAction(Oops) execAction() { /* simply explain how this command works */ gLibMessages.oopsOutOfContext; } /* this is a meta-command, so don't consume any time */ actionTime = 0 ; /* intransitive form of "oops" */ DefineIAction(OopsI) doActionMain() { /* as with OOPS with a literal, simply explain the problem */ gLibMessages.oopsOutOfContext; } /* this is a meta-command, so don't consume any time */ actionTime = 0 ; property disableHints, showHints; /* hint system - disable hints for this session */ DefineSystemAction(HintsOff) execSystemAction() { if (gHintManager != nil) gHintManager.disableHints(); else mainReport(gLibMessages.hintsNotPresent); } ; /* invoke hint system */ DefineSystemAction(Hint) execSystemAction() { if (gHintManager != nil) gHintManager.showHints(); else mainReport(gLibMessages.hintsNotPresent); } ; /* ------------------------------------------------------------------------ */ /* * Parser debugging verbs */ #ifdef PARSER_DEBUG DefineIAction(ParseDebug) execAction() { local newMode; /* * get the mode - if the mode is explicitly stated in the * command, use the stated new mode, otherwise invert the current * mode */ newMode = (onOrOff_ == 'on' ? true : onOrOff_ == 'off' ? nil : !libGlobal.parserDebugMode); /* set the new mode */ libGlobal.parserDebugMode = newMode; /* mention the change */ "Parser debugging is now <<libGlobal.parserDebugMode ? 'on' : 'off'>>.\n"; } ; grammar predicate(ParseDebug): 'parse-debug' 'on'->onOrOff_ | 'parse-debug' 'off'->onOrOff_ | 'parse-debug' : ParseDebugAction ; #endif
TADS 3 Library Manual
Generated on 5/16/2013 from TADS version 3.1.3
Generated on 5/16/2013 from TADS version 3.1.3