#charset "us-ascii"
#include "advlite.h"
/*
* ************************************************************************
* actor.t This module forms part of the adv3Lite library
*. (c) 2012-13 Eric Eve.
*. Based substantially on input.t in the adv3 library
*. Copyright (c) 2000, 2006 Michael J. Roberts. All Rights Reserved.
*
*
*
* This modules defines functions and objects related to reading input from
* the player.
*/
/* ------------------------------------------------------------------------ */
/*
* Keyboard input parameter definition.
*/
class InputDef: object
/*
* The prompt function. This is a function pointer (which is
* frequently given as an anonymous function) or nil; if it's nil,
* we won't show any prompt at all, otherwise we'll call the
* function pointer to display a prompt as needed.
*/
promptFunc = nil
/*
* Begin the input style. This should do anything required to set
* the font to the desired attributes for the input text. By
* default, we'll simply display <.inputline> to set up the default
* input style.
*/
beginInputFont() { "<.inputline>"; }
/*
* End the input style. By default, we'll close the <.inputline>
* that we opened in beginInputFont().
*/
endInputFont() { "<./inputline>"; }
;
/*
* Basic keyboard input parameter definition. This class defines
* keyboard input parameters with the real-time status and prompt
* function specified via the constructor.
*/
class BasicInputDef: InputDef
construct(promptFunc)
{
self.promptFunc = promptFunc;
}
;
/* ------------------------------------------------------------------------ */
/*
* Keyboard input manager.
*/
inputManager: PostRestoreObject
/*
* Read a line of input from the keyboard.
*
* promptFunc can either be a callback function to invoke to display the
* prompt, or a single-quoted string containing the prompt. Of course, the
* caller can simply display the prompt before calling this routine rather
* than passing in a prompt callback, if desired.
*
* If we're in HTML mode, this will switch into the 'tads-input' font
* while reading the line, so this routine should be used wherever
* possible rather than calling inputLine() or inputLineTimeout()
* directly.
*/
getInputLine(promptFunc?)
{
/* read input using a basic InputDef for the given parameters */
return getInputLineExt(new BasicInputDef(promptFunc));
}
/*
* Read a line of input from the keyboard - extended interface,
* using the InputDef object to define the input parameters.
* 'defObj' is an instance of class InputDef, defining how we're to
* handle the input.
*/
getInputLineExt(defObj)
{
/*
* If a previous input was in progress, cancel it - this must be
* a recursive entry from a real-time event that's interrupting
* the enclosing input attempt. Simply cancel out the enclosing
* read attempt entirely in this case; if and when we return to
* the enclosing reader, that reader will start over with a
* fresh read attempt at that point.
*/
cancelInputInProgress(true);
/*
* Keep going until we finish reading the command. We might
* have to try several times, because our attempts might be
* interrupted by real-time events.
*/
for (;;)
{
local result;
local timeout;
/* show the prompt and any pre-input codes */
inputLineBegin(defObj);
getInput:
/*
* Read the input. (Note that if our timeout is nil, this
* will simply act like the ordinary untimed inputLine.)
*/
result = aioInputLineTimeout(timeout);
/* check the event code from the result list */
switch(result[1])
{
case InEvtNoTimeout:
/*
* the platform doesn't support timeouts - note it for
* future reference so that we don't ask for input with
* timeout again, then go back to try the input again
* without a timeout
*/
noInputTimeout = true;
timeout = nil;
goto getInput;
case InEvtLine:
/* we've finished the current line - end input mode */
inputLineEnd();
/* return the line of text we got */
return result[2];
case InEvtTimeout:
/*
* We got a timeout without finishing the input line.
* This means that we've reached the time when the next
* real-time event is ready to execute. Simply continue
* looping; we'll process all real-time events that are
* ready to go, then we'll resume reading the command.
*
* Before we proceed, though, notify the command
* sequencer (via the command-interrupt pseudo-tag) that
* we're at the start of output text after an interrupted
* command line input
*/
"<.commandint>";
break;
case InEvtEof:
/*
* End of file - this indicates that the user has closed
* down the application, or that the keyboard has become
* unreadable due to a hardware or OS error.
*
* Write a blank line to the display in an attempt to
* flush any partially-entered command line text, then
* throw an error to signal the EOF condition.
*/
"\b";
throw new EndOfFileException();
case InEvtEndQuietScript:
/*
* End of "quiet" script - this indicates that we've
* been reading input from a script file, but we've now
* reached the end of that file and are about to return
* to reading from the keyboard.
*
* "Quiet script" mode causes all output to be hidden
* while the script is being processed. This means that
* we won't have displayed a prompt for the current
* line, or updated the status line. We'll
* automatically display a new prompt when we loop back
* for another line of input, but we have to mark the
* current input line as actually ended now for that to
* happen.
*/
inputLineInProgress = nil;
inProgressDefObj = nil;
/*
* update the status line, since the quiet script mode
* will have suppressed all status line updates while we
* were reading the script, and thus the last update
* before this prompt won't have been shown
*/
statusLine.showStatusLine();
/* back for more */
break;
case 'newGuest':
/*
* Synthetic "new guest" event from the Web UI. This
* indicates that a new user has joined the session. The
* parameter is the new user's screen name. Announce the
* new user's arrival as a real-time event, and go back
* to reading input.
*/
"<.commandint>";
libMessages.webNewUser(result[2]);
break;
case 'logError':
/*
* Synthetic "log error" event from the Web UI. The UI
* posts this type of an event when an error occurs in an
* asynchronous task, where it's not possible to display
* an error message directly.
*/
"<.commandint>\b<<result[2]>>\b";
break;
}
}
}
/*
* Pause for a MORE prompt.
*/
pauseForMore()
{
/* run the MORE prompt */
aioMorePrompt();
}
/*
* Ask for an input file.
*/
getInputFile(prompt, dialogType, fileType, flags)
{
/* ask for a file */
local result = aioInputFile(prompt, dialogType, fileType, flags);
/* return the result from inputFile */
return result;
}
/*
* Ask for input through a dialog. The arguments are the same as for
* the built-in inputDialog() function.
*/
getInputDialog(icon, prompt, buttons, defaultButton, cancelButton)
{
/* show the dialog */
local result = aioInputDialog(icon, prompt, buttons,
defaultButton, cancelButton);
/* return the dialog result */
return result;
}
/*
* Read a keystroke, processing real-time events while waiting.
* 'promptFunc' works the same way it does with getInputLine().
*/
getKey(promptFunc?)
{
local evt;
/* get an event */
evt = getEventOrKey(promptFunc, true);
/*
* the only event that getEventOrKey will return is a keystroke,
* so return the keystroke from the event record
*/
return evt[2];
}
/*
* Read an event, processing real-time events while waiting, if
* desired. 'allowRealTime' and 'promptFunc' work the same way they
* do with getInputLine().
*/
getEvent(promptFunc?)
{
/* read and return an event */
return getEventOrKey(promptFunc, nil);
}
/*
* Read an event or keystroke. 'promptFunc' works the same way it does in
* getInputLine(). If 'keyOnly' is true, then we're only interested in
* keystroke events, and we'll ignore any other events entered.
*
* Note that this routine is not generally called directly; callers should
* usually call the convenience routines getKey() or getEvent(), as
* needed.
*/
getEventOrKey(promptFunc, keyOnly)
{
/*
* Cancel any in-progress input. If there's an in-progress
* input, a real-time event must be interrupting the input,
* which is recursively invoking us to start a new input.
*/
cancelInputInProgress(true);
/* keep going until we get a keystroke or other event */
for (;;)
{
local result;
local timeout;
/* show the prompt and any pre-input codes */
inputEventBegin(promptFunc);
getInput:
/*
* Read the input. (Note that if our timeout is nil, this
* will simply act like the ordinary untimed inputLine.)
*/
result = aioInputEvent(timeout);
/* check the event code from the result list */
switch(result[1])
{
case InEvtNoTimeout:
/*
* the platform doesn't support timeouts - note it for
* future reference so that we don't ask for input with
* timeout again, then go back to try the input again
* without a timeout
*/
noInputTimeout = true;
timeout = nil;
goto getInput;
case InEvtTimeout:
/*
* We got a timeout without finishing the input line.
* This means that we've reached the time when the next
* real-time event is ready to execute. Simply continue
* looping; we'll process all real-time events that are
* ready to go, then we'll restart the event wait.
*/
break;
case InEvtEof:
/*
* End of file - this indicates that the user has closed
* down the application, or that the keyboard has become
* unreadable due to a hardware or OS error.
*
* Write a blank line to the display in an attempt to
* flush any partially-entered command line text, then
* throw an error to signal the EOF condition.
*/
"\b";
throw new EndOfFileException();
case InEvtKey:
/* keystroke - finish the input and return the event */
inputEventEnd();
return result;
case InEvtHref:
/*
* Hyperlink activation - if we're allowed to return
* events other than keystrokes, finish the input and
* return the event; otherwise, ignore the event and keep
* looping.
*/
if (!keyOnly)
{
inputEventEnd();
return result;
}
break;
default:
/* ignore other events */
break;
}
}
}
/*
* Cancel input in progress.
*
* If 'reset' is true, we'll clear any input state saved from the
* interrupted in-progress editing session; otherwise, we'll retain
* the saved editing state for restoration on the next input.
*
* This MUST be called before calling tadsSay(). Games should
* generally never call tadsSay() directly (call the library
* function say() instead), so in most cases authors will not need
* to worry about calling this on output.
*
* This MUST ALSO be called before performing any keyboard input.
* Callers using inputManager methods for keyboard operations won't
* have to worry about this, because the inputManager methods call
* this routine when necessary.
*/
cancelInputInProgress(reset)
{
/* cancel the interpreter's internal input state */
aioInputLineCancel(reset);
/* if we were editing a command line, terminate the editing session */
if (inputLineInProgress)
{
/* do our normal after-input work */
inputLineEnd();
}
/* if we were waiting for event input, note that we are no longer */
if (inputEventInProgress)
{
/* do our normal after-input work */
inputEventEnd();
}
}
/*
* Begin reading key/event input. We'll cancel any report gatherer
* so that prompt text shows immediately, and show the prompt if
* desired.
*/
inputEventBegin(promptFunc)
{
/* if we're not continuing previous input, show the prompt */
if (!inputEventInProgress)
{
inputBegin(promptFunc);
/* note that we're in input mode */
inputEventInProgress = true;
}
}
/*
* End keystroke/event input.
*/
inputEventEnd()
{
/* if input is in progress, terminate it */
if (inputEventInProgress)
{
/* note that we're no longer reading an event */
inputEventInProgress = nil;
}
}
/*
* Begin command line editing. If we're in HTML mode, we'll show
* the appropriate codes to establish the input font.
*/
inputLineBegin(defObj)
{
/* notify the command sequencer that we're reading a command */
"<.commandbefore>";
/* if we're not resuming a session, set up a new session */
if (!inputLineInProgress)
{
/* begin input */
inputBegin(defObj.promptFunc);
/* switch to input font */
defObj.beginInputFont();
/* note that we're in input mode */
inputLineInProgress = true;
/* remember the parameter object for this input */
inProgressDefObj = defObj;
}
}
/*
* End command line editing. If we're in HTML mode, we'll show the
* appropriate codes to close the input font.
*/
inputLineEnd()
{
/* if input is in progress, terminate it */
if (inputLineInProgress)
{
/* note that we're no longer reading a line of input */
inputLineInProgress = nil;
/* end input font mode */
inProgressDefObj.endInputFont();
/* notify the command sequencer that we're done reading */
"<.commandafter>";
/*
* tell the main text area's output stream that we just
* ended an input line
*/
mainOutputStream.inputLineEnd();
/* forget the parameter object for the input */
inProgressDefObj = nil;
}
}
/*
* Begin generic input. Cancels command report list capture, and
* shows the prompt if given.
*/
inputBegin(promptFunc)
{
switch(dataTypeXlat(promptFunc))
{
/* if we have a prompt, display it */
case TypeSString:
say(promptFunc);
break;
case TypeFuncPtr:
(promptFunc)();
break;
default:
/* Do nothing */
break;
}
}
/* receive post-restore notification */
execute()
{
/*
* Reset the inputLine state. If we had any previously
* interrupted input from the current interpreter session, forget
* it by canceling and resetting the input line. If we had an
* interrupted line in the session being restored, forget about
* that, too.
*/
aioInputLineCancel(true);
inputLineInProgress = nil;
inputEventInProgress = nil;
/*
* Clear the inputLineTimeout disabling flag - we might be
* restoring the game on a different platform from the one where
* the game started, so we might be able to use timed command
* line input even if we didn't when we started the game. By
* clearing this flag, we'll check again to see if we can
* perform timed input; if we can't, we'll just set the flag
* again, so there will be no harm done.
*/
noInputTimeout = nil;
}
/*
* Flag: command line input is in progress. If this is set, it means
* that we interrupted command-line editing by a timeout, so we
* should not show a prompt the next time we go back to the keyboard
* for input.
*/
inputLineInProgress = nil
/* the InputDef object for the input in progress */
inProgressDefObj = nil
/* flag: keystroke/event input is in progress */
inputEventInProgress = nil
/*
* Flag: inputLine does not support timeouts on the current platform.
* We set this when we get an InEvtNoTimeout return code from
* inputLineTimeout, so that we'll know not to try calling again with
* a timeout. This applies to the current interpreter only, so we
* must ignore any value restored from a previously saved game, since
* the game might have been saved on a different platform.
*
* Note that if this value is nil, it means only that we've never
* seen an InEvtNoTimeout return code from inputLineEvent - it does
* NOT mean that timeouts are supported locally.
*
* We assume that the input functions are uniform in their treatment
* of timeouts; that is, we assume that if inputLineTimeout supports
* timeout, then so does inputEvent, and that if one doesn't support
* timeout, the other won't either.
*/
noInputTimeout = nil
;
/* ------------------------------------------------------------------------ */
/*
* End-of-file exception - this is thrown when readMainCommand()
* encounters end of file reading the console input.
*/
class EndOfFileException: Exception
;
/* ------------------------------------------------------------------------ */
/*
* 'Quitting' exception. This isn't an error - it merely indicates that
* the user has explicitly asked to quit the game.
*/
class QuittingException: Exception
;
/* ------------------------------------------------------------------------ */
/*
* Base class for command input string preparsers.
*
* Preparsers must be registered in order to run. During
* preinitialization, we will automatically register any existing
* preparser objects; preparsers that are created dynamically during
* execution must be registered explicitly, which can be accomplished by
* inheriting the default constructor from this class.
*/
class StringPreParser: PreinitObject
/*
* My execution order number. When multiple preparsers are
* registered, we'll run the preparsers in ascending order of this
* value (i.e., smallest runOrder goes first).
*/
runOrder = 100
/*
* Do our parsing. Each instance should override this method to
* define the parsing that it does.
*
* 'str' is the string to parse, and 'which' is the rmcXxx enum
* giving the type of command we're working with.
*
* This method returns a string or nil. If the method returns a
* string, the caller will forget the original string and work from
* here on out with the new version returned; this allows the method
* to rewrite the original input as desired. If the method returns
* nil, it means that the string has been fully handled and that
* further parsing of the same string is not desired.
*/
doParsing(str, which)
{
/* return the original string unchanged */
return str;
}
/*
* construction - when we dynamically create a preparser, register
* it by default
*/
construct()
{
/* register the preparser */
StringPreParser.registerPreParser(self);
}
/* run pre-initialization */
execute()
{
/* register the preparser if it's not already registered */
StringPreParser.registerPreParser(self);
}
/* register a preparser */
registerPreParser(pp)
{
/* if the preparser isn't already in our list, add it */
if (regList.indexOf(pp) == nil)
{
/* append this new item to the list */
regList.append(pp);
/* the list is no longer sorted */
regListSorted = nil;
}
}
/*
* Class method - Run all preparsers. Returns the result of
* successively calling each preparser on the given string.
*/
runAll(str, which)
{
/*
* if the list of preparsers isn't sorted, sort it in ascending
* order of execution order number
*/
if (!regListSorted)
{
/* sort the list */
regList.sort(SortAsc, {x, y: x.runOrder - y.runOrder});
/* the list is now sorted */
regListSorted = true;
}
/* run each preparser */
foreach (local cur in regList)
{
/* run this preparser, provided it's active */
if(cur.isActive)
str = cur.doParsing(str, which);
/*
* if the result is nil, it means that the string has been
* fully handled, so we need not run any further preparsing
*/
if (str == nil)
return nil;
}
/* return the result of the series of preparsing steps */
return str;
}
/* class property containing the list of registered parsers */
regList = static new Vector(10)
/* class property - the registration list has been sorted */
regListSorted = nil
/* Flag, is this PreParser active? */
isActive = true
;
/* ------------------------------------------------------------------------ */
/*
* The "comment" pre-parser. If the command line starts with a special
* prefix string (by default, "*", but this can be changed via our
* commentPrefix property), this pre-parser intercepts the command,
* treating it as a comment from the player and otherwise ignoring the
* entire input line. The main purpose is to give players a way to put
* comments into recorded transcripts, as notes to themselves when later
* reviewing the transcripts or as notes to the author when submitting
* play-testing feedback.
*/
commentPreParser: StringPreParser
doParsing(str, which)
{
/* get the amount of leading whitespace, so we can ignore it */
local sp = rexMatch(leadPat, str);
/*
* if the command line starts with the comment prefix, treat it
* as a comment
*/
if (str.substr(sp + 1, commentPrefix.length()) == commentPrefix)
{
/*
* It's a comment.
*
* If a transcript is being recorded, simply acknowledge the
* comment; if not, acknowledge it, but with a warning that
* the comment isn't being saved anywhere
*/
if (scriptStatus.scriptFile != nil)
DMsg(note with script, 'Comment recorded. ');
else if (warningCount++ == 0)
DMsg(note without script warning, 'Comment NOT recorded. ');
else
DMsg(note without script, 'Comment NOT recorded. ');
/*
* Otherwise completely ignore the command line. To do this,
* simply return nil: this tells the parser that the command
* has been fully handled by the preparser.
*/
return nil;
}
else
{
/* it's not a command - return the string unchanged */
return str;
}
}
/*
* The comment prefix. You can change this to any character, or to
* any sequence of characters (longer sequences, such as '//', will
* work fine). If a command line starts with this exact string (or
* starts with whitespace followed by this string), we'll consider
* the line to be a comment.
*/
commentPrefix = '*'
/*
* The leading-whitespace pattern. We skip any text that matches
* this pattern at the start of a command line before looking for the
* comment prefix.
*
* If you don't want to allow leading whitespace before the comment
* prefix, you can simply change this to '' - a pattern consisting of
* an empty string always matches zero characters, so it will prevent
* us from skipping any leading charactres in the player's input.
*/
leadPat = static new RexPattern('<space>*')
/* warning count for entering comments without SCRIPT in effect */
warningCount = 0
/*
* Use a lower execution order than the default, so that we run
* before most other pre-parsers. Most other pre-parsers are written
* to handle actual commands, so it's usually just a waste of time to
* have them look at comments at all - and can occasionally be
* problematic, since the free-form text of a comment could confuse a
* pre-parser that's expecting a more conventional command format.
* When the comment pre-parser detects a comment, it halts any
* further processing of the command - so by running ahead of other
* pre-parsers, we'll effectively bypass other pre-parsers when we
* detect a comment.
*/
runOrder = 50
;
Adv3Lite Library Reference Manual
Generated on 25/04/2024 from adv3Lite version 2.0