actions.t
#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