-
Notifications
You must be signed in to change notification settings - Fork 17
The Text Processor
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 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.
directive | effect | example |
---|---|---|
i | italic | example |
b | bold | example |
u | underlined | example |
s | strike-through | |
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 |
Use cap
to capitalise the first letter of the text.
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.
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}.
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 player 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.
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.
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 player will not lose their game if she clicks on it!
Learn more about Quest 6 {link:here:https://github.com/ThePix/QuestJS/wiki}.
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.
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.
Use dateTime
to display the current in-game date and time. The format is set in settings.
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}.),
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}
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}
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}.}",
],
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.
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.
Use these to display a number in words, rather than digits.
You have {number:player:hitpoints} hit point{ifMoreThan:player:hitpoints:0:s}.
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}",
}
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.
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".
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.}
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}
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.
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.
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})
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.
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.
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.
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!
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;
});
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.
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
- First steps
- Rooms and Exits
- Items
- Templates
- Items and rooms again
- More items
- Locks
- Commands
- Complex mechanisms
- Uploading
QuestJS Basics
- General
- Settings
- Attributes for items
- Attributes for rooms
- Attributes for exits
- Naming Items and Rooms
- Restrictions, Messages and Reactions
- Creating objects on the fly
- String Functions
- Random Functions
- Array/List Functions
- The
respond
function - Other Functions
The Text Processor
Commands
- Introduction
- Basic commands (from the tutorial)
- Complex commands
- Example of creating a command (implementing SHOOT GUN AT HENRY)
- More on commands
- Shortcut for commands
- Modifying existing commands
- Custom parser types
- Note on command results
- Meta-Commands
- Neutral language (including alternatives to "you")
- The parser
- Command matching
- Vari-verbs (for verbs that are almost synonyms)
Templates for Items
- Introduction
- Takeable
- Openable
- Container and surface
- Locks and keys
- Wearable
- Furniture
- Button and Switch
- Readable
- Edible
- Vessel (handling liquids)
- Components
- Countable
- Consultable
- Rope
- Construction
- Backscene (walls, etc.)
- Merchandise (including how to create a shop)
- Shiftable (can be pushed from one room to another)
See also:
- Custom templates (and alternatives)
Handing NPCs
- Introduction
- Attributes
- Allowing the player to give commands
- Conversations
- Simple TALK TO
- SAY
- ASK and TELL
- Dynamic conversations with TALK TO
- TALK and DISCUSS
- Following an agenda
- Reactions
- Giving
- Followers
- Visibility
- Changing the player point-of-view
The User Experience (UI)
The main screen
- Basics
- Printing Text Functions
- Special Text Effects
- Output effects (including pausing)
- Hyperlinks
- User Input
The Side Panes
Multi-media (sounds, images, maps, etc.)
- Images
- Sounds
- Youtube Video (Contribution by KV)
- Adding a map
- Node-based maps
- Image-based maps
- Hex maps
- Adding a playing board
- Roulette!... in a grid
Dialogue boxes
- Character Creation
- Other example dialogs [See also "User Input"]
Other Elements
- Toolbar (status bar across the top)
- Custom UI Elements
Role-playing Games
- Introduction
- Getting started
- Items
- Characters (and Monsters!)
- Spawning Monsters and Items)
- Systema Naturae
- Who, When and How NPCs Attack
- Attributes for characters
- Attacking and guarding
- Communicating monsters
- Skills and Spells
- Limiting Magic
- Effects
- The Attack Object
- [Extra utility functions](https://github.com/ThePix/QuestJS/wiki/RPG-Library-%E2%80%90-Extra Functions)
- Randomly Generated Dungeon
- Quests for Quest
- User Interface
Web Basics
- HTML (the basic elements of a web page)
- CSS (how to style web pages)
- SVG (scalable vector graphics)
- Colours
- JavaScript
- Regular Expressions
How-to
Time
- Events (and Turnscripts)
- Date and Time (including custom calendars)
- Timed Events (i.e., real time, not game time)
Items
- Phone a Friend
- Using the USE verb
- Display Verbs
- Change Listeners
- Ensembles (grouping items)
- How to spit
Locations
- Large, open areas
- Region,s with sky, walls, etc.
- Dynamic Room Descriptions
- Transit system (lifts/elevators, buses, trains, simple vehicles)
- Rooms split into multiple locations
- Create rooms on the fly
- Handling weather
Exits
- Alternative Directions (eg, port and starboard)
- Destinations, Not Directions
Meta
- Customise Help
- Provide hints
- Include Achievements
- Add comments to your code
-
End The Game (
io.finish
)
Meta: About The Whole Game
- Translate from Quest 5
- Authoring Several Games at Once
- Chaining Several Games Together
- Competition Entry
- Walk-throughs
- Unit testing
- Debugging (trouble-shooting)
Releasing Your Game
Reference
- The Language File
- List of settings
- Scope
- The Output Queue
- Security
- Implementation notes (initialisation order, data structures)
- Files
- Code guidelines
- Save/load
- UNDO
- The editor
- The Cloak of Darkness
- Versions
- Quest 6 or QuestJS
- The other Folders
- Choose your own adventure