Skip to content

The Text Processor

ThePix edited this page Apr 26, 2021 · 50 revisions

The text processor is very similar to Quest 5.

The text processor allows authors to put codes in text that will get translated by Quest, according to the directive given and the conditions when it is printed.

To use the text processor, you can simply add a directive in curly braces in any text that gets displayed. In this simple example (the same one as the Quest 5 documentation in fact), a room description is set to say that room smells only the first time the text is printed:

This is a small dungeon.{once: There is a bad smell in here.}
                        ------------------------------------
                         ^^^^

The text processor takes the entire underlined section. The directive here is "once". Note that it is followed by a colon. Some directives take several parts; these will always be split up with colons. In this case there is just the one extra part. If this is the first time the message is show, that will be added to the text. Otherwise it will not.

NOTE: The text processor in Quest 5 has an eval directive (you can just use equals as a shortcut) that allows you to just put code into your text processor directive. This is not possible n Quest 6; running arbitrary code is a security risk in a browser so is (or should be) prohibited. As an alternative, you should create your own directives as discussed at the end of this page.

Styling

Styling just changes the appearance of the text, not its content. You can also do this with HTML/CSS, but for short sections within a paragrap this is easier and less typing. If you want to change the whole paragraph, you are better off using a CSS class with the msg function.

Here is a simple example of styling text:

This text has {i:a section} in italic.

Simple styling

directive effect example
i italic example
b bold example
u underlined example
s strike-through example
code mono-spaced font example
sup superscript example
sub subscript example
huge 200% the size
big 150% the size
small 80% the size
tiny 60% the size
smallcaps small capitals

Capitalise

Use cap to capitalise the first letter of the text.

Colours

You can also use "colour" (or "color") and "back" to set the foreground and background colour, together with a colour.

This text has {colour:red:some of it} in red.

If you want to go a bit more extreme, use the "rainbow" directive, which turns every character a different character. The simple version uses the default list, but you can provide your own list to suit the colour scheme of your page (note that you cannot nest other directives or HTML code within a rainbow directive).

Some text {rainbow:with some quite colorful writing}.
Some text {rainbow:red:blue:with some not so varied colour}.

Colours must be either standard web colour names or hexadecimal triples; for more on both see here. Bear in mind about 5% of the population has some degree of colour-blindness, so choose your colours thoughtfully.

Blur

You can blur text with the "blur" directive. This requires a number, the degree of blur. A value of 0 will look like normal, 2 or 3 are readable, higher than that, and it will be obscured.

{blur:3:If you were not so drunk you could read this}.

Fonts

Use the "font" directive to change the font family.

This text has {font:sans-serif:some of it} in a plain font.

You need to be a little bit careful to ensure the user has the font on their computer. There is a page here which gives some suggested safe fonts.

Alternatively, you can download fonts from Google in the stylesheet. You should pick out all the fonts you need, then go to the list of families at the bottom, go to "Embed", then "Import" and get some text like this (in this example I am using two font families; Krona One and Trade Winds):

@import url('https://fonts.googleapis.com/css?family=Krona+One|Trade+Winds&display=swap');

Paste it into the top of the style.css file, and you are good to go.

Dialogue

To highlight speech, and perhaps differentiate between different speakers, you can put spoken text inside a dialogue directive, which will also add quotation marks.

This has three modes. All of these put quote marks around the text for you. By default single quotes are used, but you can modify to your liking in the settings. For French style, for example, it would look like this.

settings.openQuotation:"<<",
settings.closeQuotation:">>",

Dialogue with a class

If the first parameter starts with a dot, this is taken as a CSS class, and this will be applied to the rest of the parameters. In this example the class "my-css-class" is used. If the class does not exist, the text will appear, but no styling will be applied.

"Kyle says; {dialogue:.my-css-class:Hello!}"

Dialogue specified

In the second mode, you specify the styling in the directive. For the first parameter, if it includes "i", the text will be in italic, if there is a "b" it will be in bold, if a "u" it will be underlined. The second parameter is the colour, which can be any HTML colour. These two examples both set the text to be cyan; the second also sets it to be italic and bold.

"Kyle says; {dialogue::cyan:Hello!}"
"Kyle says; {dialogue:ib:#00ffff:Hello!}"

Dialogue by object

You can also associate the styling with an object; this allows you to consistently colour-code the NPCs in your game. To do this, specify a parameter, and Quest will use its "dialogueStyle" to get the styling, which must be is the standard CSS format. In this example, Kyle's "dialogueColour" is set to magenta, Lara's to orange and italic. The text is then set to use the object set to "char" in the parameters.

w.Kyle.dialogueStyle= 'color:#ff00ff;'
w.Lara.dialogueStyle= 'color:orange;font-style:italic;'
msg("Kyle says; {dialogue:char:Hello!}", {char:w.Kyle})
msg("{dialogue:char:And hello to you!} replies Lara.", {char:w.Lara})

Exotic alphabets

Use the encode directive to translate text into another character set for messages in exotic languages. The directive requires two hexadecimal numbers, the first unicode character for upper case and lower case letters. You need a continuous block of 26 unicode characters for each set (or use the same for both upper and lower case).

Some examples:

Some text in Greek: {encode:391:3AC:The quick brown fox jumped over the lazy dog}.
Some text in Cyrillic: {encode:402:431:The quick brown fox jumped over the lazy dog}.
Some text in Armenian {encode:531:561:The quick brown fox jumped over the lazy dog}.
Some text in Devanagari: {encode:904:904:The quick brown fox jumped over the lazy dog}.
Some text in Thai {encode:E01:E01:The quick brown fox jumped over the lazy dog}.
Some text in Tibetan {encode:F20:F20:The quick brown fox jumped over the lazy dog}.
Some text in Khmer {encode:1780:1780:The quick brown fox jumped over the lazy dog}.
Some text in Javan {encode:A985:A985:The quick brown fox jumped over the lazy dog}.
Some text in Nko {encode:7C1:7C1:The quick brown fox jumped over the lazy dog}.

Please use responsibly; these character sets are a part of some people's culture.

Link

You can link to other web pages (and so to other games) with the link directive. The first parameter is the text that will appear on the page, the second is the address. Note that the page will open in a new tab, so the user will not lose their game if she clicks on it!

Learn more about Quest 6 {link:here:https://github.com/ThePix/QuestJS/wiki}.

Popup

You can have a link in your text that will toggle a box of text. This could be used to offer a kind of glossary - click on a term to see a definition. The popup directive has two parts, the first being the link text and the second being the text to display.

There is a sizeable {popup:desert:A desert is a large dry area.} to the west.

Text modifying

These directives change the content of the text.

Several are marked *; these take the name of an object as a parameter, but you can also use "player" and "here" to represent the player object and the current location, whatever their actual names.

Date and time

Use dateTime to display the current in-game date and time. The format is set in settings.

Random

The random directive will show one of the following options, picked at random:

{random:one:two:three}

As with Quest 5, The text processor is invoked when text is printed to screen and this means random text will be randomised every time it is printed. That is good if you want some random event to add interest, but not so good if the ball has a random colour each time the player looks at it. In the latter situation, you should invoke the text processor when the item is created, with the processText function. This example shows how you could set the examine attribute of a ball:

examine:processText("The ball is {random:blue:red:green}.),

Images

The img directive can be used to include an image. By default, this should be in the images folder (settings/images, unless you have changed it), however, if the filename has a forward slash, Quest will assume you have included the path yourself. You can optionally include a title (as in the example below) and "alt" text (not included in the example).

{img:box.png:An image of a box}

command/cmd

The command directive (or just cmd) will add a hyperlink; when clicked it will pass the command to the parser.

{cmd:get boots}
{cmd:drop hat:drop the hat}

exits and objects and rooms

There are a number of directives that are designed to be used with the room template. This is how Quest decides the format for a room description. This is the default, an array of four strings, giving four paragraphs in the description.

  roomTemplate:[
    "#{cap:{hereName}}",
    "{terse:{hereDesc}}",
    "{objectsHere:You can see {objects} here.}",
    "{exitsHere:You can go {exits}.}",
  ],

The first line uses hereName to get the name of the current room (with "the" added if not flagged as a proper name). The cap directive makes it start with a capital letter. The # at the start of the line makes it into a heading (and is not actually a feature of the text processor).

The second line is the room description, using hereDesc, together with terse so the description is omitted in TERSE mode.

The third line lists the items here. The objectsHere directive means that if no objects are here this will not appear. The fourth line does the same for exits.

This offers a lot of flexibility. Here is an alternative version that puts everything in a single paragraph.

  roomTemplate:[
    "You are in {hereName}. {terse:{hereDesc}} {objectsHere:You can see {objects} here.} {exitsHere:You can go {exits}.}",
  ],

once and notOnce

Text with the "once" directive will only be seen once, the first time the text is displayed. Conversely, if "notOnce" is used, the text is only seen after the first time.

Yu need to be a little bit careful with spaces with these and other directives that are shown conditionally (the various if directives are similar). You need to consider what spaces will be required in either situation. Usually it is best to have a space inside the text directive, at the start of the text, as indicated below.

The room is a mess.{once: That is a surprise!} Clothing is strewn all around.
                         ^

If the text is used, there will be a space before it and after it. If the text is not used, there will still be a single space between the two sentences. This example has the extra text as a fragment within a sentence. The comma needs to be inside the text directive.

The room is a mess{once:, which is a surprise}. Clothing is strewn all around.

show*

To show an attribute, use show. You then need the object, then the attribute name, all separated by colons.

You have {show:player:hitpoints} hit points.

You can actually omit the "show", and for compatibility with Quest 5, if "show" is omitted, you can use a dot to separate the object name and attribute name, so these will give the same result as the above:

You have {player:hitpoints} hit points.
You have {player.hitpoints} hit points.

If the attribute is a function, it should return a string; the function will be called and the resultant string inserted into the text.

number* and ordinal*

Use these to display a number in words, rather than digits.

You have {number:player:hitpoints} hit point{ifMoreThan:player:hitpoints:0:s}.

select*

Instead of showing the value of the attribute, you can use the number to select a value from a list. Both number and list must be on the same object. In this example, the object "Kyle" is given two attributes, one for the list and one for the value. Changing the value of Kyle.colour will cause the message to change when next printed.

  w.Kyle.colours = ['red', 'green', 'blue']
  w.Kyle.colour = 1
  msg("Kyle is {select:Kyle:colours:colour}.")

This example shows how you can have an object's description change when its state changes.

createItem("robot" {
  loc:"lab",
  state:0,
  descs:[
    "A bunch of junk... but perhaps you can make something from it?",
    "It looks ugly, but it is a working robot.",
    "The robot's eyes are glowing red - never a good sign.",
    "A pile of junk that used to be a robot.",
  ],
  examine:"{select:robot:descs:state}",
}

if* and ifNot*

So far so good. At this point it deviates from Quest 5.

These will test the value of an attribute. It needs the object name, then the attribute name.

If the attribute is a Boolean (and if it does not exist, in which case it will be taken to have the value false) it then needs the string to show if true, then, optionally, the string to show it false.

Player has that odd thing: {if:player:someOddAtt:yes:no}

If the attribute is an integer or string, it needs the value to test against, before the values to show.

Is the player exactly forty? {if:player:age:40:yes:no}

The ifNot directive is the same, but works in reverse.

ifLessThan* and ifMoreThan*

These are similar to "if" and "ifNot", but can only be used with integer values.

Is the player forty or over? {ifMoreThan:player:age:39:yes:no}

See also the example in "number".

ifHere* and ifNotHere*

These are also similar to "if" and "ifNot", but depend on whether the named object is in the room (i.e., isAtLoc returns true.

This is a messy room.{ifHere:puddle: There is a puddle of water on the floor.}

money*

Use the money directive to display money, using the format set up in settings.js. This can be used with a number (it assume a whole number) or with the name of an object. If an object, the objects selling price is used if held by the player, or the selling price otherwise (see the MERCH template), if these are set. If not available, the price of the object is used (if no price is set you will get an error message). If the object is the player, the player's "money" attribute is used.

You can use a dollar sign as a short cut for this directive.

The carrot is {money:carrot}
The carrot is {$:carrot}
You see {$:12}

Using Parameters

Now we leave Quest 5 far behind. Using parameters is more complicated, but very powerful. If you can get you head around it, there are all sorts of tricks you can use, in particular to make responses that are neutral with respect to the objects involved.

For more on using these for neutral language, see also here.

You can send a set of parameters to the msg function, as a dictionary. The text processor directive can then access these to modify your text.

param

The simplest way to use parameters is just to access them via the param directive. You can either provide a string, which is used as is; or an object or object name together with an attribute name, to have the attribute inserted.

  msg("Here is some {param:my_text}.", {my_text:"interesting text"});
  -> Here is some interesting text.
  msg("Here is a {param:my_item:alias}.", {my_item:"coin"});
  -> Here is a gold coin.
  msg("Here is a {param:my_item:alias}.", {my_item:w.coin});
  -> Here is a gold coin.

If the attribute is a function, it will be run, and the value returned will be inserted. Any additional values will be passed (as strings) to the function. Any that match a parameter will first be converted to its value.

This rather contrived example is taken from the unit tests:

  w.book.func1 = function() { return "test1" }
  w.book.func2 = function(a, b) { return "test2(" + a + ", " + b + ")" }
  w.book.func3 = function(a) { return "It is " + w[a].alias + " reading the book." }
  msg("Simple text: p2={param:item:func1}", {item:"book"})
  msg("Simple text: p2={param:item:func2:one:two}", {item:"book"})
  msg("Simple text: p2={param:item:func3:char}", {item:"book", char:"Kyle"})

->
Simple text: p2=test1
Simple text: p2=test2(one, two)
Simple text: p2=It is Kyle reading the book.

Use number or ordinal to convert a number to words.

Other directives

Some of the directives already discussed supports params; "show", "money" and the various conditional directives. If the name of the object is actually the name of a parameter, this will be used instead.

msg("{nm:item:the:true} is {$:carrot}", {item:w.carrot})

nm and nms

To print the name of an object (i.e., what it is called, its alias, rather than the "name" attribute), use the nm directive. Behind the scenes this uses the getName function in lang-en.js (so it is language neutral), and you can specify if "the" or "a" should be prepended as required. You can add true as a further parameter to have it capitalised, as in the third and fourth examples.

  msg("Here is {nv:my_item:the}.", {my_item:"Kyle"});
  -> Here is Kyle.
  msg("Here is {nv:my_item:the}.", {my_item:"umbrella"});
  -> Here is the umbrella.
  msg("{nv:my_item:a:true}.", {my_item:"Kyle"});
  -> Kyle is here.
  msg("{nv:my_item:a:true}.", {my_item:"umbrella"});
  -> An umbrella is here.

To get the possessive form, use nms. This will append 's to the name, or replace it with "your" or "my" if appropriate.

  msg("It is {nms:chr:the} book.", {chr:game.player})
  -> It is your book.
  msg("It is {nms:chr:the} book.", {chr:w.Kyle})
  -> It is Kyle's book.

cj, nv and pv

To conjugate a verb, use cj (conjugate; which just gives the correct form of the verb), nv (noun-verb; prepends the name of the object to the verb) or pv (pronoun-verb; prepends the correct pronoun to the verb). Add a third parameter, true, to capitalise.

  // Using the name
  msg("{nv:my_item:be} here.", {my_item:"Kyle");
  -> Kyle is here.
  msg("{nv:my_item:be} here.", {my_item:"shoes");
  -> Shoes are here.
  msg("{nv:my_item:be} here.", {my_item:game.player);
  -> You are here.

  // Using the pronoun
  msg("{pv:my_item:be} here.", {my_item:"Kyle");
  -> He is here.
  msg("{pv:my_item:be} here.", {my_item:"shoes");
  -> They are here.
  msg("{pv:my_item:be} here.", {my_item:game.player);
  -> You are here.

ob, sb, ps, pa, rf

These will produce the pronoun for the given item. You can again add "true" as an additional parameter to capitalise.

  msg("Kyle is {pa:my_item} bear.", {my_item:game.player);
  -> Kyle is your bear.
  msg("Kyle is {pa:my_item} bear.", {my_item:"Lara");
  -> Kyle is her bear.
  • ob: The object; I, you, he, she, it or they
  • sb: The subject; me, you, him, her, it, them
  • ps: The possessive; mine, yours, his, hers, its, theirs
  • pa: The possessive adjective; my, your, his, her, its, theirs
  • rf: The reflexive; myself, yourself, etc.

pa2

The "pa2" directive expects to be given two items. It will give the possessive adjective on the first object if they use different pronouns, or the possessive form of the name if they use the same pronouns. If that does not make sense, take a look at the examples, which use the same string, but different characters.

msg("'Please stop!' exclaims {nm:chr1:the} when {nv:char:rips} {poss:chr1:chr2} book to shred.", {chr1:w.Kyle, chr2:game.player});
-> 'Please stop!' exclaims Kyle when you rip his book to shred.

msg("'Please stop!' exclaims {nm:chr1:the} when {nv:char:rips} {poss:chr1:chr2} book to shred.", {chr1:w.Kyle, chr2:w.Boris});
-> 'Please stop!' exclaims Kyle when Boris rips Kyle's book to shred.

In the first, it is clear "his" refers to Kyle, as the other person is you. In the second it could refer to Kyle or Boris, so we want the name to make it clear.

Are similar directives needed for other pronouns? Let me know!

Customising

You can add your own text processor directives using the tp.addDirective function. This needs a name and a function. The function should take a string array as a parameter (the input text, split on colons), and return a string.

Here is an example:

  tp.addDirective("fancy", function(arr, params) {
    return '<span style="font-family:Montserrat">' + arr.join(":") + "</span>"; 
  });

Note that the parameter arr is joined with colons, as this is how it was broken up. You may want to use the first one to set a value, as in this example:

  tp.addDirective("fancy2", function(arr, params) {
    const font = arr.shift();
    return '<span style="font-family:' + font + '">' + arr.join(":") + "</span>"; 
  });

This example calls a function; if the function returns true, the second section is shown, otherwise the third is.

tp.addDirective("cloakHere", function(arr, params) {
  return cloakHere() ? arr[0] : arr[1]
});

You can use this to do anything. It could just insert a standard piece of text, similar to a macro. This is currently the way to do "eval" in the quest 5 text processor (JavaScript can handle eval but it is considered insecure, so I am trying to avoid it).

  tp.addDirective("title", function(arr, params) {
    return setings.title; 
  });

params

In addition to any parameters you send, params will also contain:

  • tpOriginalString: The original string
  • tpFirstTime: Will be true the first time this string has been done.

Circumventing the text processor

The msg function automatically uses the text processor, if you want to not use it, use the rawPrint function.

If you want to use the text processor, but want the special characters in you text, they are special codes you can put in the text, which the text processor will ignore, but the msg command will convert to the character when printing to screen.

Character Code
{ @@@lcurly@@@
} @@@rcurly@@@
: @@@colon@@@

A Note About String Interpolation

JavaScript has its own equivalent of the text processor, and there are times you may find it easier to use that. To use string interpolation, you have to use backticks to surround your string, rather than single or double quotes. You can then put your JavaScript expression inside curly braces with a dollar sign at the start.

These two are the same:

"The answer is " + myValue + "."
`The answer is ${myValue).`

You can mix both types. In this example, the description only includes the clock if the clock's display attribute is equal to the DSPY_SCENERY. We need to insert that value into the text processor command.

createRoom("kitchen", {
  desc:`A clean room{if:clock:display:${DSPY_SCENERY}:, a clock hanging on the wall}.`,
...

The clock is scenery until picked up. At that point it stops being part of the room description.

Note that JavaScript string interpolation will happen when the string is created; the text processor does its magic when the string is displayed. We need to use the text processor in that last example because the string is created at the start of the game, and we need it to change depending on the game state at the time it is used. DSPY_SCENERY, however, is a constant, so we can safely have that set in stone when the game starts.

Tutorial

QuestJS Basics

The Text Processor

Commands

Templates for Items

See also:

Handing NPCs

The User Experience (UI)

The main screen

The Side Panes

Multi-media (sounds, images, maps, etc.)

Dialogue boxes

Other Elements

Role-playing Games

Web Basics

How-to

Time

Items

Locations

Exits

Meta

Meta: About The Whole Game

Releasing Your Game

Reference

Clone this wiki locally