Messages
In broad terms, a message is anything a game displays to the player to convey information about what has just happened, or just failed to happen. In the context of discussing actions we are, of course, particularly concerned with messages relating to the consequences or failures of actions, the game's responses to player commands, but it's worth sketching the broader picture first.
Unlike the adv3 library, adv3Lite does not use a transcript (a means of buffering all output and then finally displaying it at appropriate moments after giving library code and game code a chance to meddle with it). Instead, for the most part, the output of double-quoted strings and say() statements goes straight to the screen (after passing through a number of output filters). In particular instances the library code does capture or store messages in string properties and variables for use later; this allows the adv3Lite library to produce some of the effects of the adv3 library (e.g. the formatting of implicit action reports) without recourse to a full-blown transcript. But for the most part game authors can assume that any messages they ask to be displayed will be displayed more or less straight away; this removes many of the complexities that working with the transcript can sometimes cause in adv3.
For a variety of reasons, however, the adv3Lite library does not generally display its messages in the form of raw text (e.g. "You can't take that; it's fixed in place") but via message properties and special macros (BMsg()and DMsg(), which will be described in more detail below). The reason for this is threefold:
- To facilitate translation of the library into other languages.
- To make it easier for game authors to customize the messages the library produces.
- To enable messages to be written in such a way that they still display correctly whichever tense the game is in (past or present) and whoever is performing the action (the player character or an NPC) or whether the game is written in the first, second or third person.
The first of these reasons is unlikely to be much of a concern for game authors (unless perhaps you are a pioneer author wanting to be the first to write an adv3Lite game in your non-Anglophone native tongue). The second may be of limited concern to game authors, but at the very least game authors need to understand the mechanisms the library employs so they can customize library messages, and they may want to employ similar techniques when defining their own classes and actions to ensure maximum reusability of their code. The third reason may or may not be of concern to game authors. If your game never changes tense and no action in your game will ever be directly performed by an NPC (and your game never changes player character), then there's probably no reason not to write all your custom messages directly (e.g. "You can't take that; it's fixed to the wall."). If your player character may change, or your game does change tense (e.g., switching to the past tense for flashback scenes), or actions may be directly performed by an NPC (broadly speaking, if an action may be performed when gActor is another actor and not gPlayerChar), then you may need to write your messages more flexibly, employing some of the techniques used in the library.
The adv3Lite library uses two different mechanisms to handle library messages, one derived from the adv3 library and the other from the Mercury library. The adv3 method uses properties of the libMessages object defined in the language-specific english.t module to store and display English-language messages. Such messages are used only for the WebUI interface (which requires them, since the WebUI uses a library in common with adv3) and for the menu system (which is again borrowed from adv3). It is unlikely that game authors will ever need to be concerned with the libMessages object (unless you really want to customize some of these messages), but anyone wishing to translate the adv3Lite library into another language will need to be aware of it.
All other library messages are generated by the DMsg() and BMsg() macros, which are generally used in line with the code that needs them. This is the principal mechanism used for defining virtually all the library messages that will be relevant to game authors, and in below we shall go on to describe how it works.
First, though, it's worth emphasising that the DMsg() and BMsg() mechanisms are primarily intended for library messages, i.e. messages that the library uses to provide a default response (such as "Taken" or "You can't attach the widget to anything") when a game author hasn't provided a more specific response. There may be occasions when game authors want to customize these default messages, so it's useful to know how these mechanisms work, but note that this isn't the way you'd normally go about customizing specific responses on specific objects (such as "You gingerly pick up the vase" or "The cheese smells exceptionally ripe"). For that kind of thing you'd typically just override an appropriate message property (or method) with your own string (e.g. smellDesc = "The cheese smells exceptionally ripe. "), using, for example, one of the properties provided for the purpose on the Thing class or one of the methods described in the discussion of Action Results.
DMsg() and BMsg()
DMsg() and BMsg() (think of them as standing for 'Display Message' and 'Build Message') are the mechanisms derived from the Mercury code for handling customizable messages. The difference between them is that DMsg() displays a message while BMsg() returns the text of a message in a single-quoted string (to be stored in an object property or variable, for example).
The basic format in which these macros are used is:
Where key is an arbitrary (or rather, user-defined) key to identify the message, msg is the text of the message as a single-quoted string (which may and usually will contain message substitution parameters, see below), and [, params...] is an optional list of one or more parameters (up to 9) corresponding to the placeholders {1}, {2}, {3}... {9} in msg. The key value is used to identify the message so that an alternative message with the same key value may be used instead if one is defined on an active CustomMessage object (for which see below). This may all become a bit clearer with a couple of examples, which we shall take from the library's definition of dobjFor(Take) on the Thing class:
class Thing: Mentionable ... dobjFor(Take) { preCond = [touchObj] verify() { if(isFixed) illogical(cannotTakeMsg); if(isDirectlyIn(gActor)) illogicalNow(alreadyHeldMsg); if(gActor.isIn(self)) illogicalNow(cannotTakeMyContainerMsg); logical; } check() { /* * Check that the actor has room to hold the item s/he's about to * pick up. */ checkRoomToHold(); } action() { /* * If we have any contents hidden behind us or under us, reveal it * now */ revealOnMove(); actionMoveInto(gActor); } report() { DMsg(report take, 'Taken. | {I} {take} {1}. ', gActionListStr); } } cannotTakeMsg = BMsg(fixed in place, '{The subj dobj} {is} fixed in place. ') alreadyHeldMsg = BMsg(already holding, '{I}{\'m} already holding {the dobj}. ') cannotTakeMyContainerMsg = BMsg(cannot take my container, '{I} {can\'t} {take} {the dobj} while {i}{\'m} {1} {him dobj}. ', objInPrep) ... ;
First note that we define three messages that might be used at the verify stage, cannotTakeMsg, alreadyHeldMsg and cannotTakeMyContainerMsg as properties of Thing, so they can be easily overridden by game authors (either on the Thing class or on individual objects). Each of these three message properties is populated by a BMsg macro that evaluates to a single-quoted string. Each of them is given a key value relating to the text or purpose of the message (fixed in place, already holding, and cannot take my container). Note that these key values are not wrapped in quote marks (the BMsg macro takes care of that for us). These key values can be used by a CustomMessage object to replace the text of these messages with something else, for what the BMsg() and DMsg() macros do is first look for an active CustomMessage object that defines a message with the corresponding key (such as fixed in place). If they can't find one, they use msg instead. Since the adv3Lite library doesn't define any CustomMessage objects, by default it will use the message defined in the msg property instead (unless a game author overrides it by providing a CustomMessage object that defines the message in question).
The module english.t defines a number of parameter substitutions that can be used to fit the message to its context. We'll give a full list of these below; for now we'll simply illustrate the principle by describing how they work in the particular examples above.
We'll start with BMsg(fixed in place, '{The subj dobj} {is} fixed in place. ') In this message '{the subj dobj}' will be replaced by the theName of the current direct object in the subjective case and {is} will be replaced by the appropriate form of the verb "to be" to match the direct object (in English nothing marks the name of a noun as being in the subjective case, but the message builder needs to know that it's the subject of the sentence, so that '{is}' is meant to agree with it). This will result in messages like 'The stone ball is fixed in place. ' or 'The stone pillars are fixed in place. ' (if the game is in the present tense) or 'The stone ball was fixed in place. ' or 'The stone pillars were fixed in place. ' (if the game is in the past tense).
Next, let's look at BMsg(already holding, '{I}{\'m} already holding {the dobj}. ') Here '{I}' stands for the actor, {\'m} will be replaced with the appropriately contracted form of the verb "to be", and {the dobj} will the replaced with the theName property of the direct object, which the message builder will consider to be the grammatical object of the sentence. For a second-person game in the present tense this may result in message like "You're already holding the red ball. " For a first-person game in the past tense it would come out as "I was already holding the red ball. "
Now let's consider the slightly more complex BMsg(cannot take my container, '{I} {can\'t} {take} {the dobj} while {i}{\'m} {1} {him dobj}. ', objInPrep). Here, as before, '{I}' becomes the appropriate form of the actor ('I', 'You', 'He', or whatever the case may be). '{can\'t} then becomes either "can\'t" or "could not" depending on whether the game is in the present or the past tense. The second '{i}' is again replaced with the appropriate pronoun for the actor, but because this time '{i}' is in lower-case, the pronoun won't be capitalized (unless it's 'I'). The '{1}' will be replaced with the first parameter following the message string, in this case objInPrep ('in' or 'on' or whatever the case may be). Finally '{him dobj}' will expand to the appropriate objective-case pronoun to refer to the direct object (either 'him' or 'them', depending on whether the direct object is singular or plural).
Finally, there's the DMsg() macro in the report() method: DMsg(report take, 'Taken. | {I} {take} {1}. ', gActionListStr); In this case the library uses the DMsg() macro to display the message straight away rather than going via a message property (a) because there's no point in overriding the report() method on individual objects, and (b) because it's easy enough to override a report method on a class should the need arise (or else to define a CustomMessage object to do the job). The main feature to note here, though, is the vertical bar (|) in the middle of the message. When this is present we're actually defining two messages: the one to the left of the bar ('Taken. ') is for use when it should be obvious to the player what object his/her command referred to (if you type TAKE RED BALL, the report 'You take the red ball' could seem just a little pedantic; 'Taken' will normally suffice). The one to the right of the bar ('{I} {take} {1}. ') is for use when it may not be immediately clear to the player what his/her command has acted on, e.g. TAKE BALL (when there's more than one ball in scope), TAKE BALLS (when it there may be several balls around) or TAKE ALL (which might pick anything). The general rule is that the library uses the longer, fuller form of the message (where two messages are defined with a vertical bar like this) either when the parser has had to diambiguate (by choosing the highest scoring object among two or more candidates) or when it has matched objects to an indefinite plural (such as BALLS or ALL). In the message '{I} {take} {1}. ', the {I} once again replaced with the pronoun appropriate to the actor, {take} becomes the appropriate form the verb "to take" ('take', 'takes' or 'took'), and the {1} is replaced with the value of gActionListStr, which contains a list of the objects just acted on. The DMsg in reportDobjTake() might this generate a transcript like this:
>take black cube Taken. >take balls You take the red ball and the green ball. >take blotter and ink Taken. >take fountain You take the fountain pen. >take all You take the pad of paper, the diary, the half-eaten bar of chocolate, the glass paper-weight and the safety pin.
CustomMessages Objects
We have already mentioned that messages can be customized by using CustomMessages objects. The time has now come to look at how these work.
Language extensions and games can use CustomMessages objects to define their own custom messages that override the default English messages used throughout the library.
Each CustomMessages object can define a list of messages to be customized. This lets you centrally locate all of your custom messages by putting them all in a single object, if you wish. Alternatively, you can create separate objects, if you prefer to keep them with some other body of code they apply to. In either case, the library gathers them all up during preinit.
For most purposes the CustomMessages objects you create can be anonymous objects; the only reason for giving a CustomMessage object a name would be if you wanted to change its properties during the course of a game (so that you'd need to be able to refer to the CustomMessage object in your code). The properties you will or may need to define on your CustomMessage object are the following:
priority: The priority determines the precedence of a message defined in this object, if the same message is defined in more than one CustomMessages object. The message with the highest priority is the one that's actually displayed. The library defines one standard priority level: 100 is the priority for language module overrides. Each language module would need to provide a translated set of the standard library messages, via a CustomMessages object with priority 100. (The default English messages defined in-line throughout the library via DMsg() and BMsg() macros effectively have a priority of negative infinity, since any custom message of any priority overrides a default.) Games will generally want to override all library messages, including translations, so the default priority is set to 200.
active: Determines whether this customizer is active. If you want to change the messages at different points in the course of the game, you can use this to turn sets of messages on and off. For example, if your game includes narrator changes at certain points, you can create separate sets of messages per narrator. By default, we make all customizations active, but you can override this to turn selected messages on and off as needed. Note that the library consults this every time it looks up a message, so you can change the value dynamically, or use a method whose return value changes dynamically.
messages: The list of messages being customized. This can contain any number of messages; the order isn't important. Each message is defined with a Msg() macro: Msg(id key, 'Message text'), .... The "id key" is the message ID that the library uses in the DMsg() or BMsg() message that you're customizing. (DON'T use quotes around the ID key.) The message text is a single-quoted string giving the message text. This can contain curly-brace replacement parameters.
For example, here's how we might define a CustomMessages object to replace the dobjFor(Take) messages we looked at above:
CustomMessages messages = [ Msg(report take, 'Snatched. | You grab {1}. '), Msg(fixed in place, 'Idiot; any fool can see {the subj dobj} {is} firmly nailed down. '), Msg(already holding, 'In case you hadn\'t noticed, you\'re already holding {the dobj}. '), Msg(cannot take my container, 'Great idea. Just how do you propose to pick up {the dobj} while you\'re right {1}? ') ] ;
Note that we can't change the parameters used by the message strings (the ones that {1} and the like refer to) but that we can continue to use the ones used in the original DMsg() and BMsg() macros we're replacing; in fact what the CustomMessages object does is effectively to substitute a different msg parameter into the DMsg/BMsg(key, msg, params...) set-up, leaving the original params in place.
While we're on the subject of the numerical parameter substitutions ({1}, {2} and the like) and the parameters they can refer to, up until now all the examples we've shown of them have used single-quoted string values in the param list, but the values can also be numbers (or numerical expressions) and objects (in which case the name property is used in the substitution). For example, the following is legal if not exactly useful:
DMsg(two things, 'The {1} and the {2} are {3} separate objects. ', redBall, blueBall, 1 + 1);
Note that some care needs to be taken with a CustomMessages object that's meant to supply variable messages via embedded expressions. For example the following CustomMessages object won't work as expected:
CustomMessages messages = [ Msg(not important, '<<one of>>Leave it alone<<or>>It\'s not worth messing with<<or>>{The cobj} {is} totally unimportant<<shuffled>>. ') ] ;
This is to do with the way the TADS 3 language evaluates embedded expressions in single-quoted strings, and with the fact that a CustomMessages object stores its data in a LookupTable. As soon as that LookUp table is referenced, all its elements are evaluated, and any single-quoted strings in the table are then stored as the single-quoted string value they come out as at that moment (in this case, as a string without any embedded expressions but containing just one of the options. The way round this is to wrap the string in an anonymous function, like so:
CustomMessages messages = [ Msg(not important,{: '<<one of>>Leave it alone<<or>>It\'s not worth messing with<<or>>{The cobj} {is} totally unimportant<<shuffled>>. ' }) ] ;
In fact, the second parameter of any Msg(), BMsg() or DMsg() macro can always be an anonymous function that returns a single-quoted string instead of just a single-quoted string, but this is the kind of occasion on which this is most likely to be useful.
Finding Message IDs
Being able to change the text of messages using a CustomMessages object is all very well, but how do you go about finding the message id of the message you need to change?
One way would be to search all the source files (which is not so bad if you happen to be using Windows Workbench and can make use of its search facilities to search your entire project). This may work, but it's not ideal, so adv3Lite now provides a couple of alternatives.
- You can use the Messages tab of the Library Reference Manual. This will bring up alphabetical list of all the message ID's of the DMsg() and BMsg() definitions in the library. Clicking on any of these will bring up a complete list of all the DMsg() and BMsg() definitions in the main panel, which hopefully your browser will allow you to search. These are linked to their definitions in the relevant source code file, to allow you to check the context of their definition.
- You can use the CSV file generated from Jerry Ford's discovery tool. This contains a list of all the BMsg and DMsg definitions which you can sort, search and otherwise play around with in any way you find useful, provided you have a spreadsheet program (such as Excel) that can open .csv files.
Message Substitution Parameters
The previous section introduced the point that we can use message substitution parameters in conjunction with the DMsg() and BMsg() macros. In fact they can be used anywhere, with the limitation that the numerical substitution parameters ({0}...{9}) have no meaning outside a DMsg() or BMsg() macro, since there are no further parameters for them to refer to. If you want to use these numerical parameters outside BMsg() and DMsg() (i.e. without specifying a key value you have no use for) you can use the dmsg() and bmsg() functions, which work like DMsg() and BMsg() but don't take the first (key) parameter, e.g. dmsg('You take {1}. ', gActionListStr);. To make full use of the message substitution parameters, however, you have to know what they all are, so we shall next provide a list of them all.
In the following table obj can be one of: actor, dobj (the current direct object of the action), iobj (the current indirect object of the action), cobj (the current object — direct or indirect — of the action), the globalParamName of an object, or a temporary parameter name defined with the gMessageParams(obj) macro.
Parameter | Explanation |
{i} | The actor's name in the subjective case ('I' or 'you' for the pc in a first or second person game) |
{me} | The actor's name in the objective case ('me' or 'you' for the pc in a first or second person game) |
{my} | The possessive pronoun for the actor ('my' or 'your' for the pc in a first or second person game) |
{mine} | The possessive noun for the actor ('mine' or 'yours' for the pc in a first or second person game) |
{myself} | The reflexive pronoun for the actor (e.g. 'myself' or 'yourself') |
{here} | If the actor is the PC this translates to 'here' for the present tense and 'there' for any other tense; otherwise it translates to nothing at all. |
{then} | This translates to 'now' for the present tense and 'then' for the past. |
{now} | This translates to 'now' for the present tense and nothing otherwise. |
{the subj obj} | This translates to the theName property of obj and marks obj as the subject of the sentence. |
{the obj} | This translates to the theName property of obj and marks obj as the object of the sentence. |
{the obj's} | This translates to the possessive of obj (e.g. "the red ball's", "Bob's"). |
{a subj obj} | This translates to the aName property of obj and marks obj as the subject of the sentence. |
{an subj obj} | This translates to the aName property of obj and marks obj as the subject of the sentence. |
{a obj} | This translates to the aName property of obj and marks obj as the object of the sentence. |
{an obj} | This translates to the aName property of obj and marks obj as the object of the sentence. |
{in obj} | The objInName of obj (e.g. "in the box" or "on the table"). |
{inprep obj} | The objInPrep of obj (e.g. "in" or "on"). |
{out obj} | The objOutOfName of obj (e.g. "out of the box" or "off the table"). |
{he obj} | The subjective case pronoun for obj (e.g. 'he', 'she', 'it', or 'they'). |
{him obj} | The objective case pronoun for obj (e.g. 'him', 'her', 'it', or 'them'). |
{her obj} | The possessive pronoun for obj (e.g. 'his', 'her', 'its', or 'their'). |
{hers obj} | The possessive noun for obj (e.g. 'his', 'hers', 'its', or 'theirs'). |
{herself obj} | The reflexive pronoun for obj (e.g. 'himself', 'herself', 'itself', or 'themselves'). |
{himself obj} | The reflexive pronoun for obj (e.g. 'himself', 'herself', 'itself', or 'themselves'). |
{itself obj} | The reflexive pronoun for obj (e.g. 'himself', 'herself', 'itself', or 'themselves'). |
{that subj obj} | The demonstrative pronoun for obj as subject ('that' or 'those'). |
{that obj} | The demonstrative pronoun for obj as object ('that' or 'those'). |
{dummy} | Expands to nothing but acts as a singular subject marker for a verb that follows to agree with (e.g., 'There{dummy}{\'s} nothing {here}.') |
{plural} | Expands to nothing but acts as a plural subject marker for a verb that follows to agree with (e.g., 'There{plural} {is} no people {here}.') |
{prev} | Expands to nothing but acts as a subject marker to make a verb that follows agree with a previous list(see below) |
{am} | Verb 'to be' conjugated to agree in tense and number with the immediately preceeding subject (e.g. am, are, is, was, were) |
{are} | Verb 'to be' conjugated to agree in tense and number with the immediately preceeding subject (e.g. am, are, is, was, were) |
{is} | Verb 'to be' conjugated to agree in tense and number with the immediately preceeding subject (e.g. am, are, is, was, were) |
{isn't} | Negated verb 'to be' contracted and conjugated to agree with the immediately preceeding subject (e.g. am not, aren't, isn't, wasn't, weren't) |
{amn't} | Negated verb 'to be' contracted and conjugated to agree with the immediately preceeding subject (e.g. am not, aren't, isn't, wasn't, weren't) |
{aren't} | Negated verb 'to be' contracted and conjugated to agree with the immediately preceeding subject (e.g. am not, aren't, isn't, wasn't, weren't) |
{'m} | Contracted verb 'to be' agreeing with the immediately preceeding subject (e.g. 'm, 're, 's, was, were) |
{'re} | Contracted verb 'to be' agreeing with the immediately preceeding subject (e.g. 'm, 're, 's, was, were) |
{'s} | Contracted verb 'to be' agreeing with the immediately preceeding subject (e.g. 'm, 're, 's, was, were) |
{was} | Past verb 'to be' agreeing with the immediately preceeding subject (e.g. was, had been, will have been) |
{were} | Past verb 'to be' agreeing with the immediately preceeding subject (e.g. was, had been, will have been) |
{wasn\'t} | Negated past verb 'to be' agreeing with the immediately preceeding subject (e.g. wasn't, hadn't been, won't have been) |
{weren\'t} | Negated past verb 'to be' agreeing with the immediately preceeding subject (e.g. wasn't, hadn't been, won't have been) |
{wasnot} | Negated past verb 'to be' agreeing with the immediately preceeding subject (e.g. was not, had not been, will not have been) |
{werenot} | Negated past verb 'to be' agreeing with the immediately preceeding subject (e.g. was not, had not been, will not have been) |
{\'ve} | Contracted verb 'to have' agreeing with the immediately preceeding subject (e.g. I've, I'd, you'll have) |
{haven\'t} | Negated contracted verb 'to have' agreeing with the immediately preceeding subject (e.g. haven't, hadn't, won't have) |
{hasn\'t} | Negated contracted verb 'to have' agreeing with the immediately preceeding subject (e.g. haven't, hadn't, won't have) |
{don't verb} | Contracted do not verb, where verb is an infinitive (e.g. if verb='come': don't come, doesn't come, didn't come) |
{doesn't verb} | Contracted do not verb, where verb is an infinitive (e.g. if verb='come': don't come, doesn't come, didn't come) |
{do not verb} | Do not verb, where verb is an infinitive (e.g. if verb='come': do not come, does not come, did not come) |
{does not verb} | Do not verb, where verb is an infinitive (e.g. if verb='come': do not come, does not come, did not come) |
{can} | Appropriate form of can (can or could) |
{cannot} | Appropriate form of can (cannot or could not) |
{can't} | Appropriate form of can (can't or couldn't) |
{must verb} | Conjugate must with verb, where verb is an infinitive (e.g. if verb='come': must come, had to come) |
{verb} | Conjugate verb, where verb can be 'have', 'do', 'run', 'think', 'take', 'hold', 'say' or one of over 200 other irregular verbs. |
{s/d} | Conjugate verb ending -s/-d (e.g. close{s/d} -> close, closes, closed). |
{s/ed} | Conjugate verb ending -s/-ed (e.g. open{s/ed} -> open, opens, opened). |
{es/ed} | Conjugate verb ending -es/-ed (e.g. smash{es/ed} -> smash, smashes, smashed). |
{s/?ed} | Conjugate verb ending -s/-ed with final consonant doubled (e.g. stop{s/?ed} -> stop, stops, stopped). |
{ies/ied} | Conjugate verb ending -y/-ies/-ied (e.g. cr{ies/ied} -> cry, cries, cried). |
{lb} | literal left brace { |
{rb} | literal right brace } |
{bar} | vertical bar symbol | |
{s} | expands to 's' if the previous parameter was plural, or nothing otherwise. |
{es} | expands to 'es' if the previous parameter was plural, or nothing otherwise. |
{ies} | expands to 'ies' if the previous parameter was plural, or 'y' otherwise. |
{1}...{9} | substitute arguments 1 to 9 |
{# n} | spells the number given by integer argument n (1-9) |
{and n} | shows the list given by integer argument n (1-9) as a basic "and" list ("x, y, and z") |
{or n} | shows the list given by integer argument n (1-9) as a basic "or" list ("x, y, or z") |
{a|b} | displays b if the game is in the past tense and a otherwise. |
A couple of these may be made clearer by a couple of examples. dmsg('The available colours are {and 1}', ['red', 'blue', 'green']); would display "The available colours are red, blue and green.". dmsg('There are {# 1} colour{s} available. ', 3); would display "There are three colours available. "
The verb parameter is generally used like this: DMsg(okay take, '{I} {take} {the dobj}. '). But how do you know which verbs the library defines? You can see them by looking at the definition of englishCustomVocab.verbParams in english.t. If the verb you need isn't there (and it actually needs to be defined as a parameter instead of plain text) you can add more verbs with a CustomVocab object, which is explained in more detail below. The library defines most common irregular verbs for use in the {verb} parameter. For regular verbs, see further below.
As mentioned above the obj parameter can be one of actor, dobj, iobj, cobj, a globalParamName or a temporary parameter name set using gMessageParams(). A globalParamName is simply a string property you can define on any Thing and use later as a message parameter to refer to that thing, e.g.:
george: Actor 'George; tall thin; man' @hall "He's a tall thin man. " isHim = true globalParamName = 'george' ; ... "{The subj george} {seems} upset with you. ";
In this case there's little advantage in using the globalParamName over simply writing "George seems upset with you." But one reason you might want to use the globalParamName here is that George might start out as 'the tall man' when you first meet him and only become 'George' on closer acquaintance; in such a case '{The subj george}' will expand to either 'The tall man' or 'George' as appropriate (using the current theName property of the george object).
The dummy tag is used to mark a grammatical subject that appears in the message to be displayed but is not represented by anything in the game, so that there's no further message parameter to attach it to. A simple example would be:
'There{dummy}{\'s} nothing {here}.'
This marks 'There' as the subject of the sentence so that it will come out as "There's nothing here" or "There was nothing there" according to tense.
For a more complex example, consider the following cases: "Fred knows that exercise is good for him" and "Fred knows everything about himself", where Fred is an actor that can change (so we might,for example, want it to vary to "Freda knows that exercise is good for her" or "I know everything about myself"). The trouble is that the routine that expands these message substitution parameters isn't smart enough to distinguish between the structure of these two sentences unless you help it out. So in the first example:
"{He actor} {know} that exercise is good for {him actor}. ";
The expansion would go wrong because the subject of the verb in the subordinate clause is "exercise", but the routine that does the expansion can only go by what's been explicitly marked as a subject, and the last one marked is "{He actor}", so that it appears to the library that {him actor} is the object of "{He actor} {know}", which, contrary to what we want here, would be expanded to "He knows himself." There's nothing in the string "that exercise is good for" that signals the actual sentence construction to the expansion routine; from the software's point of view it's structually identical to:
"{He actor} {know} everything about {him actor}. ";
Where "He knows everything about himself" would be correct. But in the first example we need to tell the game what the actual grammatical subject of the subordinate clause is, which we do by marking it with {dummy}, thus:
"{He actor} {know} that {dummy}exercise is good for {him actor}. ";
This tells the parameter expansion routine that "exercise" and not "actor" is now the subject of "is".
Finally, we mentioned that the obj parameter could also be a temporary parameter name. The Action method setMessageParam() lets you define such a parameter, but for convenience, the library defines the gMessageParams() macro for setting one or more parameters whose names exactly match their local variable names. In other words, if you call this macro like this:
gMessageParams(obj, cont);
then you'll get one parameter with the text name 'obj' whose expansion will be the value of the local variable obj, and another with text name 'cont' whose expansion is the value of the local variable cont.
Conjugations and Tenses
Many of the message substitution parameters listed above are designed to secure agreement betweeb a verb and its grammatical subject, in whichever tense the game happens to be. If you need to write code which might vary by person and/or tense, you'll typically write messages like:
"{I} climb{s/ed} over the fence. "; "{The subj dobj} {breaks} in two. ";
In the first of these strings, "climb" is a regular verb, so we can conjugate it by adding {s/ed} to the end. In the second case "breaks" is not a regular verb (the past is "broke" not "breaked") so we need to surround the entire verb in braces (either {break} or {breaks}, it doesn't matter which), so that adv3Lite can look it up in its table of irregular verbs. The English-language specific part of the library knows over two hundred irregular verbs, so most of the common ones should be covered. If you need to conjugate an unusual irregular verb in your game that the library doesn't know about, you'll need to define it in a CustomVocab object (on which see below).
Some verbs have both a regular and an irregular form, often with different meanings, so if you're using a verb of this sort you need to choose the message parameter substitution that gives you the form you want, e.g.:
"{I} hang{s/ed} the murderer on the gallows. "; "{I} {hang} the picture on the wall. "; "{I} {lie} down on the bed. "; "{I} lie{s/d} about my age. ";
In either case all six tenses are available (e.g. I hang the murderer on the gallows, I hanged the murderer on the gallows, I have hanged the murderer on the gallows, I had hanged the murderer on the gallows, I shall hang the murderer on the gallows, I shall have hanged the murderer on the gallows), according to the setting of Narrator.tense).
Regular verbs are verbs whose past simple and past participle end in d or ed (e.g. stopped, opened, closed). The adv3Lite recognizes five variants of these, which between them take five variants of the {s/d} ending:
- {s/d} e.g. close{s/d}, giving [I] close, [he] closes, [he] closed.
- {s/ed} e.g. open{s/ed}, giving [I] open, [he] opens, [he] opened.
- {es/ed} e.g. cross{es/ed}, giving [I] cross, [he] crosses, [he] crossed.
- {ies/ies} e.g. cr(ies/ies}, giving [I] cry, [he] cries, [he] cried.
- {s/?ed} e.g. stop{s/?ed}, giving [I] stop, [he] stops, [he] stopped.
In the last of these, note that we write the parameter substitution string as {s/?ed} not {s/ped}; that is, we use a question mark and leave the library to work out which consonant it needs to double.
To recap, the rule to follow is this: if the verb you want to conjugate is regular, use one of the {s/d} type endings to conjugate it (e.g. 'open{s/ed}'}. If the verb is irregular, surround the complete verb in braces to conjugate it (e.g. '{shut}'). A few verbs have both regular and irregular forms, sometimes with different meanings, sometimes with the same meaning. In such cases use an {s/d} type ending if you want the regular form and surround the complete verb in braces if you want the irregular form, for example:
"{I} {dive} into the pool. " // past "You dove into the pool", preferred in American English "{I} dive{s/d} into the pool. " // past "You dived into the pool", preferred in British English
Normally, either form of verb conjugated with a message parameter substitution requires a subject, supplied by a previous message parameter substitution, to agree with, such as the '{I}' in "{I} {lie} down on the bed." Occasionally though you may want to write a message that works in more than one tense, but where there's no message parameter substitution to represent the subject of the verb, for example "There is nothing much here/There was nothing much there." This can be written:
"There {is} nothing much {here}. ";
In such a case, where the message parameter substitution mechanism finds no subject for the verb (in this case, {is}) to agree with, it defaults to a third-person singular subject (via the dummy_ object), so that the above statement is equivalent to:
"There {dummy} {is} nothing much {here}. ";
Where {dummy} explicitly makes the dummy_ object the subject of the sentence (since the dummy_ object has an empty name, its presence doesn't cause any additional text to be displayed). Since the English-language message parameter substitution mechanism defaults to the dummy_ object when it can't find a subject, you don't have to specify {dummy} explicitly. If, however, you required a grammatically plural subject, you would need to supply it explicitly using {plural}. For example, to write a message that comes out as "There are no sausages here/There were no sausages there/There have been no sausages here/There had been no sausages there/There will be no sausages here/There will have been no sausages here" according to tense, you'd need to write:
"There {plural} {are} no sausages here. ";
If your game is only ever going to switch between the present tense and the past tense (and never going to use any of the others), then you can use an alternative way of varying the verb between the present and past forms, either long-windedly through the tSel('present', 'past') macro, or more concisely with the {present|past} message substitution, for example:
"There {is|was} nothing much {here}. "; "There {are|were} no sausages {here|there}. ";
One other point to consider is that the special parameter substitutions {don't verb} and {must verb} may need the past participle of verb in some tenses (Perfect, Past Perfect and Future Perfect). Very few games are likely to make use of these tenses in practice, so for the great majority of games this is unlikely to be an issue, and in any case the library already knows how to form the past participle of most English irregular verbs. For regular verbs you can supply the past participle ending in square brackets with the don't and must parameter substitutions if you feel you need it: e.g. {don't open[ed]}, {must stop[?ep]} or {don't close[d]}.
When to Use Message Parameter Substitutions
In case this is all starting to look just a little complicated, it's worth remembering that in game code you may not actually need to use message parameter substitutions all that much. They're primarily needed in library code to make sure that standard library messages work with whatever tense and objects a game may happen to define. In your own game code you'll mainly be able to write straightforward strings like:
"You shut the door. "; "There are no sausages here. ";
You only need to consider using message parameter substitutions where your text might vary, either because you don't know what the subject of a verb is going to be or because your game changes tense. More generally, message parameter substitutions are only required in any of the following circumstances:
- You are writing messages for a library extension, rather than a game (in which case you'd also need to use DMsg() or BMsg()).
- Your game changes tense at least once (e.g. because there's a flash-back in the past tense while most of the game is in the present tense), and some of your messages may appear in either part of the game.
- You are allowing your players to choose which tense or person to play the game in (as, for example, in my game Shelter from the Storm), so your messages need to work with more than one tense and/or person.
- You are writing handlers for additional actions defined in your game, or custom responses to built-in actions, and the corresponding messages have to work with varying subjects (such as an actor which could be the player character or an NPC, or with a direct object which could be singular or plural).
- You are customizing library messages via a CustomMessages object and you want to ensure that your new messages are sufficiently flexible.
If you're writing a game (rather than a library extension) and your game never changes tense or player-character person (the most common case), circumstances 1-3 won't apply.
Lists and {prev}
It's sometimes useful to be able to display a list of items in your output text. The basic function the adv3Lite library provides for this purpose is makeListStr(lst, nameProp?, conjunction?). The second two parameters are optional. The first, lst, is the list of items for which you want a formatted list. The second, nameProp, is the property to use on each item when building the list. The default value is &aName, which means you get a list of the form "a duck, a green bottle, and an odd boot". If you wanted "the duck, the green bottle, and the odd boot" you'd supply &theName as the value of this parameter. The optional conjunction parameter is a single-quoted string containing the conjunction you want between the last two elements of the list (assuming the list has more than one element). The default value is 'and' but you could, for example, supply it as 'or'. Whichever way you call it, makeListStr() returns a single-quoted string containing the duly formatted list.
For example, suppose we have a pond whose contents are a duck, a green bottle and an old boot. Then we could write:
/* lst1 becomes 'a duck, a green bottle and an old boot' */ local lst1 = makeListStr(pond.contents); /* lst2 becomes 'the duck, the green bottle and the old boot' */ local lst2 = makeListStr(pond.contents, &theName); /* lst3 becomes 'the duck, the green bottle or the old boot' */ local lst3 = makeListStr(pond.contents, &theName, 'or');
If we just want the first form embedded in a string, we can use the << list of * >> string template, for example.
/* Displays "You see a duck, a green bottle and an old boot in the pond. */ "You see <<list of pond.contents>> in the pond. ";
If pond.contents were an empty list, then the previous code would output "You see nothing in the pond". This may be just what you want, or you may want to test that your list has any elements in it (e.g. pond.contents.length > 0) before trying to display your list, e.g.:
"<<if pond.contents.length > 0>>You see <<list of pond.contents>> in the pond. <<else>>The pond seems quite clear of debris.<<end>>> ";
This is all very well if you are preceeding your list with a verb phrase like "You see" of which the list is the object, but what if you want the list to be the subject of a verb, and you don't know how long the list will be (so you don't know if the verb needs to be singular or plural in form)? For this purpose you can use the {prev} tag, which acts as a stand-in for the list you've just displayed (whether you use makeListStr() or <<list of *>>). So for example, you could write:
/* This gives "A duck, a green bottle and an old boot are floating on the pond." */ "\^<<list of pond.contents>> {prev} {is} floating on the pond. "; /* Don't do this; it'll produce a run-time error! */ "\^<<list of pond.contents>> {is} floating on the pond. ";
Note that you must use the {prev} tag here, or the {is} tag (or the tag for whichever verb you want to use) won't have a subject to agree with, which will result in a run-time error when your game tries to display the string. The {prev} tag doesn't output anything at all, it just gives the library a singular or plural dummy noun for the next verb to agree with (as appropriate).
Because this is a relatively common case, with the verb 'to be' you can also use the form <<list of * is>>, for example:
/* This also gives "A duck, a green bottle and an old boot are floating on the pond." */ "\^<<list of pond.contents is>> floating on the pond. ";
This avoids the need to remember to use {prev}, but with any other verb you would still need to use it, for example:
* This gives "A duck, a green bottle and an old boot sit on the pond." */ "\^<<list of pond.contents>> {prev} {sit} on the pond. ";
Finally, you may occasionally want the verb to precede the list, as in "Floating around on the pond are a duck, a green bottle and an old boot." You can do this with the << is list of * >> embedded expression (but only for the verb 'to be'), in which case you once again don't need to use {prev} or another verb tag. For example:
"Floating around on the pond <<is list of pond.contents>>. ";
CustomVocab
Although the library defines over two hundred irregular verbs you can use for the verb parameter, you may occasionally need additional ones. You can see which ones the library defines by looking at the definition of englishCustomVocab.verbParams in english.t. If you need to define more you can do so by defining a CustomVocab object like this:
CustomVocab verbParams = [ 'yell/yells/yelled', 'fight/fights/fought', 'break/breaks/broke/broken' ] ;
The verbParams property is defined as is a list of strings, using the following template: 'infinitive/present3/past/past-participle'. The 'infinitive' is the 'to' form of the verb (to go, to look, to see), but *without* the word 'to'. 'present3' is the third-person present form (has, goes, sees). 'past' is the past tense form (went, looked, saw). 'past-participle' is the past participle form; this is optional, and is needed only for verbs with distinct past and past participle forms (e.g., saw/seen, went/gone). Most regular verbs — those with the past formed by adding -ed to the infinitive — have identical past and participle forms.
The other properties you can define on a CustomVocab object are:
irregularPlurals: Irregular plural list. This is a list of words with plurals that can't be inferred from any of the usual spelling rules. The entries are in pairs: singular, [plurals]. The plurals are given in a list, since some words have more than one valid plural. The first plural is the preferred one; the remaining entries are alternates. For example the library definition of its irregularPlurals list begins:
irregularPlurals = [ 'calf', ['calves', 'calfs'], 'elf', ['elves', 'elfs'], 'half', ['halves', 'halfs'], 'hoof', ['hooves', 'hoofs'], 'knife', ['knives'], ...
specialAOrAn: usually the indefinite article is 'a' for a word that starts with consonant and 'an' for a word that starts with a vowel, but there are a few exceptions. The library lists ['an heir', 'an honest', 'an honor', 'an hors', 'an hour', 'a one', 'a ouija', 'a unified', 'a union', 'a unit', 'a united', 'a unity', 'a universal', 'a university', 'a universe', 'a unicycle', 'a usage', 'a user']. You can define more of these on a CustomVocab object (following the same format) if you need them.