Some Output and Input Issues
Outputting text to the screen in adv3Lite is mostly pretty straightforward. Since, unlike adv3, there is no transcript as such, text in a double-quoted string (or a say(txt) statement) is generally output straight to the screen, thereby obviated most of the output problems that can vex adv3 game authors.
About the one case you might have to worry about is text containing HTML markup, especially if that markup contains quotation marks (e.g. <FONT NAME="Verdana">). The problem is that by default one of the adv3Lite output filters converts all straight quotes into "curly" or "typographical" quotes, so that " can become “ or ” and ' can become ‘ or ’, thereby messing up the HTML. This is also an issue with the aHref() function that can be used to produce clickable command links in your output (since it uses HTML markup). This was more of a problem in earlier versions of adv3Lite. The adv3Lite library is now better at handling quotation marks in HTML mark-up without user intervention so it may be you won't experience any problems with this. But just in case you do, there are a couple of ways round it.
One is to use the htmlSay(txt) function. This will output txt (given as a single-quoted string) without any conversion of straight quotes to typographical quotes, so it's safe to use with HTML mark-up or anything else that needs to preserve the straight quotes.
The other, which you need to use if you want to output text using a double-quoted string, is to disable and enable the cquotes output filter before and after outputting your text, like this:
cquoteOutputFilter.deactivate(); "<FONT name='Verdana'>No curly quotes here!</FONT>"; cquoteOutputFilter.activate();
The library makes use of a number of other output filters which you may occasionally find useful in your own game code. The most convenient way to use them is probably via the appropriate methods of outputManager.curOutputStream (which you can abbreviate to gOutStream):
- watchForOutput(func): Watch the stream for output. It's sometimes useful to be able to call out to some code and determine whether or not the code generated any text output. This routine invokes the given callback function, monitoring the stream for output; if any occurs, we'll return true, otherwise we'll return nil.
- captureOutput(func, [args]): Call the given function, capturing all text output to this stream in the course of the function call. Return a string containing the captured text.
A simple example of the second of these might be:
local str = gOutStream( {: "Hello World!" } );
Which would result in str containing 'Hello World!'. A more practical example might be:
local str = gOutStream( {: myObj.doSomething() } );
When we don't know in advance what output doSomething() will produce.
The library's typographicalOutputFilter inserts an n-space between the closing punctuation of one sentence and the start of the next. The property typographicalOutputFilter property sentenceSpacer allows this to be changed to something else, such as a single normal space.
The library's text formatting can get in the way of displaying an ASCII diagram (typically made of characters like - | / and \). To display such a diagram you can use the function showPreformatted(diag), where diag can be either a single-quoted string or a list of single-quoted strings that make up the diagram line by line. For more details consult the Technical Manual.
Certain aspects of how text is presented to players are controlled by style tags. These are used in the library (but could also be used in game code) to define how certain messages should be displayed. Style tags take the form <.xxx> ... <./xxx>; for example, the library defines the notification message to be displayed in response to NOTIFY ON or NOTIFY OFF as: Style tags are defined as objects of the StyleTag class, which have the following properties: So, for example, the notification style tag is defined in the library as: This allows game code to readily modify the style tags the library uses. For example, to enclose notifications in square brackets rather than parentheses, you could just define:<.p>
The use of style tags to control the appearance of room descriptions is explained in the chapter on Rooms. The other style tags defined in the library are:Style Tags
showNotifyStatus(stat)
{
DMsg(show notify status, '<.notification>Score notifications are
currently <<stat ? 'on' : 'off'>>.<./notifcation> ');
}
notificationStyleTag: StyleTag 'notification'
openText = '('
closeText = ')'
;
modify notificationStyleTag
openText = '['
closeText = ']'
;
You can use <q>...</q> to output smart quotes that alternate between double and single typographical quote marks according to the depth of nesting. You can use <sq>...</sq> and/or <dq>...</dq> to force the output of single or double typographical quote marks respectively.
Some game may wish to alter the banner window layout, for example to create a window dedicated to displaying pictures or inventory listings. This can be done with the BannerWindow class that comes with adv3Lite (and is identical to its adv3 counterpart), but most game authors will probably find it easier to use the CustomBannerWindow extension, which takes care of a lot of the busy-work
Hyperlinks
The standard way to disply a hyperlink which can be clicked on an HTML interpreter is to use the function aHref(href, txt?, title?, flags = 0)
The href parameter gives the text of the command to be executed when the clink is linked, e.g. 'JUMP'. If the display text txt is included, we'll generate the entire link including the <A HREF> tag, the hyperlinked text contents, and the </A> end tag. Thhe optional title text is displayed in the interpreters stats bar. If the text is omitted, we'll simply generate the <A HREF> tag itself, leaving it to the caller to display the text and the </A> The optional 'flags is a combination of AHREF_xxx flags indicating any special properties of the hyperlink. At present the only flag defined in the library is AHREF_Plain, which removes any special formatting from the hyperlink text.
If we want to customize the formatting of a hyperlink, we can uae the function aHrefFmt(fmt, href, txt?, title?, flags = 0), where fmt is a string defining our custom hyperlink format in the form 'open text|close text', for example '<b><u><FONT COLOR=GREEN>|</FONT></u></b>'.
Some Input Issues
inputManager
You may want to look at the inputManager methods for ways of pausing output and requesting input, and also at the article on Some Common Input/Output Issues in the Technical Manual.
.Parser Queries
When the parser doesn't understand a player's command it will often ask a clarificatory question, such as "Which do you mean, the red ball or the blue ball?" in response to X BALL. Occasionally the parser may then need to decide whether the player's response is intended to be an answer to the query or a fresh command.
For example, consider the following exchange:
>X WALL Which do you mean, the east wall or the west wall? >EAST
Is the player tellling the parser than s/he means the east wall, or is s/he issuing a new command to go east? The parser's interpretation here is governed by the priority property of the ParseErrorQuestion class. If this is set to true (the default) then the parser will assume that in such cases the player is responding to the immediately preceding query (e.g., that s/he means to refer to the east wall). If game code overrides it to nil, then the parser will make the opposite assumption.
Note that this only applies if the player's input could be interpreted either way. So, for example, if instead of typing EAST the player typed GO EAST this would be taken as a command to GO EAST in any case.
StringPreParser
It is sometimes useful to alter the player's input before passing it to the Parser to interpret. For this purpose you can use a StringPreParser and define its doParsing() method:
myPreParser: StringPreParser
doParsing(str, which)
{
/* do stuff here */
return str;
}
Here, str is the string typed by the player (possibly adjusted by one or more previous StringPreParsers). The which parameter gives some indication of the context of what the player just typed and can be one of:
- rmcDisambig: the player has just responded to a disambiguation prompt.
- rmcAskObject: the player has just responded to a request to supply a missing object to complete a command.
- rmcCommand: the player has entered a new command at the command prompt.
Note that even if which is rmcDisambig or rmcAskObject the player may have entered a complete new command instead of responding to the request to disambiguate or to supply a missing noun. You code will need to check for this possibility.
The doParsing() method should then return the (same or adjusted) string which will passed on to the Parser to interpret. Alternatively it can return nil to signal that the StringPreParser has dealt with the player's input in full, so the Parser can ignore it. For example, the adv3Lite library defines a commentPreParser thus (in order to field play-testers' comments on beta versions of your game):
/* ------------------------------------------------------------------------ */ /* * 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
Note the use of the runOrder property to determine the order in which this StringPreParser is consulted in relation to any other StringPreParsers that have been defined. StringPreParsers are run in ascending order of their runOrder property (i.e. lowest first); the default value of runOrder is 100.
Note that StringPreParsers also have an isActive property; this can be set to nil on individual StringPreParsers if you want to temporarily disable them.
Finally, note how the commentPrefix property of the commentPreParser defined above can be overridden to change the character (or characters) used to introduce a play-tester's comments.
As an example of a StringPreParser that (sometimes) changes the text passed to it into something else, here's the queryPreParser defined in the adv3Lite library (its function is to change commands like WHERE'S THE SALT into a form like WHERE IS THE SALT, that the parser can more easily understand):
/* * For queries, turn an apostrophe-s form into the underlying qtype plus is so * that the grammar defined immediately above can be matched. */ queryPreParser: StringPreParser doParsing(str, which) { local s = str.toLower(); /* First, check that this looks like a query */ if(s.startsWith('a ') || s.startsWith('ask ') || s.substr(1, 3) is in ('who', 'wha', 'whe', 'why', 'how')) { str = s.findReplace(['what\'s','who\'s', 'where\'s', 'why\'s', 'when\'s', 'how\'s'], ['what is', 'who is', 'where is', 'why is', 'when is', 'how is'], ReplaceOnce); } return str; } ;As an alternative to using a StringPreparser, you may often find it easier to use RemapCmd. This can often provide similar functionality to a StringPreparser while being simpler to use if the aim is to interpret an unusual or non standard command input.
