PawScript Reference

Every PawScript variable, statement, operator and function, with examples.

PawScript comes in two forms — expressions (small <<…>> snippets that return a value or insert a value into your text) and scripts (usually more than one line, used to change aspects of the game, such as the values of your Tracked Items). This page covers both: anything marked scripts only works only in a script.

New to PawScript? Start with the Beginner’s Guide for expressions or Writing Scripts in PawScript for scripts.

This page is the complete lookup for when you know roughly what you want. Try expressions in the PawScript Expression Playground, and scripts in the PawScript Playground.

Nothing matches that filter.

Variables

The values a script or expression reads. There are two kinds: native variables the game always provides ($player and $game), and tracked items that you create in your world.

$player

The player character — always available.

$player: name: "Aragorn" skills: swordsmanship: 5 archery: 3 skills_and_levels: "Swordsmanship (level 5), Archery (level 3)"
$player.name

The player character's name.

$player.skills

A map of each adventure skill (by its slug) to the player's level in it. Skills the player hasn't raised default to 3. Use $game.skill_map to turn a slug back into its display name.

$player.skills_and_levels

Comma-separated list of every adventure skill with the player's current level in parentheses. Useful in AI-prompt templates.

$game

The current game state — always available.

$game: turn_number: 12 ai_model: "Gryphon" skill_list: "Swordsmanship, Archery, Magic" difficulty_list: "Trivial, Easy, Medium, Hard, Legendary" skill_map: swordsmanship: "Swordsmanship" archery: "Archery" magic: "Magic" skill_example: "Swordsmanship" difficulty_example: "Medium"
$game.ai_model

Name of the AI model the player has chosen, as shown in the main menu.

$game.difficulty_example

An example difficulty description (Medium). Used by prompt templates.

$game.difficulty_list

Comma-separated list of difficulty descriptions used by the skill system.

$game.skill_example

First skill name (in adventure order) — handy for prompt templates that show one example skill.

$game.skill_list

Comma-separated list of every skill defined for this adventure.

$game.skill_map

Map from skill slug to its display name. Useful when you've got a slug from $player.skills and need the friendly name back.

$game.turn_number

Current turn number, starting at 1 on the opening turn.

Tracked items

Everything else you reference — $party, $gold, $inventory — is a tracked item you define in your world. You choose its name and shape (a number, some text, or a structured YAML value) and read or change it the same way as the native variables: <<$gold>>, $party.count(), $inventory.item(1).name. The native names above are read-only; your tracked items are the things a script exists to change.

Statements

The building blocks of a script — the lines that loop, decide and remember. Statements only exist in scripts; they never appear inside a <<…>> expression.

for each $item in $listscripts only

Repeat the indented lines once for every item in a list or map

Loops over a list or a map, one item at a time. The indented lines underneath run once per item, with the loop variable holding the current one — so $item.hp += 5 really changes that item. To repeat something a set number of times, loop over range(n).

if conditionscripts only

Run the indented lines only when a condition is true

Checks a condition and runs the indented lines underneath only when it is true. Conditions are ordinary PawScript expressions — $gold >= 50, $party.count() > 2, and so on — and you can join them with and / or.

else if conditionscripts only

Try another condition when the if above didn't match

Chains onto an if at the same indentation. Its condition is only checked when everything above it didn't match, and you can add as many as you need.

elsescripts only

What to do when nothing above matched

Closes an if chain at the same indentation — its lines run only when none of the checks above matched.

set $name = valuescripts only

Make a temporary variable that lasts only for this run

Creates a script-local variable — a scratch value that lives only while the script runs and is never saved. Handy for adding things up. set can't name a tracked item — assign those directly with $item = ….

Operators

Symbols and keywords that combine or compare values. The comparison and logical ones, plus the filter keywords below, are what you use inside .where(...), if(...) and the yes/no tests.

Arithmetic

+

Add (numbers)

Adds two numbers together. Note, to join two pieces of text, use & (or format_text()) — + is only for numbers.

-

Subtract

Subtracts the right value from the left.

*

Multiply

Multiplies two numbers.

/

Divide

Divides the left value by the right. Returns a decimal if the result isn't a whole number — wrap with .round() or .format_number() if you want to control how it's displayed.

%

Remainder after division

The "modulo" operator — the leftover after dividing left by right. Useful for "is this a multiple of N?" tests, or for cycling through values on each turn.

^

Power, e.g. 4^2 = 16

Raises the left value to the power of the right. Two-cubed is 2^3 = 8, the area of a square is $side^2.

Text

&

Join text together

Joins values into one piece of text ("concatenation"). Both sides are turned into text first, so you can mix words and numbers freely — "HP " & $health becomes "HP 20". It binds looser than the maths operators, so 1 + 2 & " points" is "3 points". For longer sentences with several values, format_text() is often tidier. In a script, &= appends to a text value — $log &= " ...and then we fought.".

Comparison

=

Equal to

True if both sides are the same value. Works for numbers, strings, booleans. Use inside if(...) or .where(...).

!=

Not equal to

True if the two sides differ.

<

Less than

True if the left value is strictly less than the right.

>

Greater than

True if the left value is strictly greater than the right.

<=

Less than or equal

True if the left value is less than or equal to the right.

>=

Greater than or equal

True if the left value is greater than or equal to the right.

Logical

and

Both conditions must be true

Combines two truthy expressions — the result is true only if both sides are true. Short-circuits: if the left is false, the right isn't evaluated.

or

Either condition can be true

The result is true if either side is true. Short-circuits: if the left is true, the right isn't evaluated.

not

Negation — flips true to false and vice versa

Inverts a truthy expression. There's also a .not() chain method that does the same thing — not $foo and $foo.not() are interchangeable.

in

Test whether a value is in a list (inside a filter)

Used inside a filter condition — .where(...), .any(...), .every(...) or .none(...) — to test whether a value appears in a list. The right side must be a list. (The bare word in only works inside those filters. Outside a filter, use the .in() method — $skill.in($unlocked) — or $unlocked.contains($skill).) To check whether a map has a particular key, use .contains("key") on the map instead.

Filter keywords

These only appear inside a filter — .where(...), .any(...), .every(...), .none(...):

Functions

Top-level functions are written on their own (round(...)); the rest are chained onto a value with a dot ($name.upper()). Remember: brackets mean “use the function”, so .count() counts, while .count looks for a field called count.

Top-level

format_text(template)

Fill in a template with values

Take a piece of text with {placeholders} in it and fill each placeholder in with the value of an expression. Handy for sentences that mix several variables with fixed wording. Use {$variable} for a variable, or {expression} for a calculation.

if(condition, then, else)

Pick one of two values based on a test

Test the first value; if it's true, return the second value, otherwise return the third. Useful for sentences that change depending on game state.

first_available(a, b, ...)

The first value that has something in it

Walk through the values left to right and return the first one that exists and isn't blank. A value that can't be worked out — because a field or item is missing — is simply skipped so it's safe to list paths that might not exist. Handy for fall-through defaults: try a nickname, then a name, then "Stranger".

choose(value, match1, result1, ..., default)

Match a value against options, like a multi-way switch

Compare the first value against each "match" and return the "result" paired with the first one that's equal. If nothing matches, return the final unpaired value as a default. Handy for mapping a class, difficulty, or status to a wording.

try(expr, fallback)

Try an expression, fall back if it fails

Evaluate the first expression; if anything goes wrong (missing variable, divide-by-zero, type error, etc.), use the fallback instead. Stops a single bad reference from breaking a whole sentence.

random()

A random number between 0 and 1

Returns a fresh random number each time it's evaluated. Useful for percent-chance tests when combined with comparisons.

dice_roll(sides, count)

Roll dice — the spelled-out form of 3d6 / d20

Roll count dice, each with sides sides, and add them up. The friendly, named version of the dice shorthand: dice_roll(6) is the same as 1d6, and dice_roll(6, 3) is the same as 3d6. Both arguments are optional — sides defaults to 6 and count to 1 — so a bare dice_roll() rolls one six-sided die.

random_between(from, to)

A random whole number between two values (both ends included)

Returns a fresh random whole number from from up to to, with both ends possible — so random_between(1, 6) is a six-sided die. The first number must not be greater than the second.

seed(n)

Make the random results that follow repeatable (a testing aid)

Sets the starting point for every random result that comes after it — random(), dice, random_between(), .shuffle() and friends — so a run can be reproduced exactly. It exists primarily for testing: put seed(42) at the top of a script in the playground and every run gives the same rolls. Take it out for real play, or the game will roll the same numbers every time.

range(count)

A list of whole numbers, 1 up to count

Makes the list 1, 2, … up to count. Most often used to repeat something a set number of times with for each $i in range(3). To start somewhere other than 1, see range(from, to).

range(from, to)

A list of whole numbers, from…to (both ends included)

Makes the list of consecutive whole numbers from one value to another, INCLUDING both ends — range(2, 5) is 2, 3, 4, 5.

list(item, ...)

Make a list from the given values (empty if you give none)

Builds a list out of the values you pass. list() is an empty list to fill in later (add to it in a script with $x.append(...)); the values can be mixed, like list("gold", 42, "gem").

record()

A new empty record — a value with named fields — to fill in

Makes an empty record you then fill in by assigning named fields. In a script: set $hero = record() then $hero.name = "Steven". Handy for building a new entry before adding it to a list with $list.append(...).

now_utc()

The current real-world time, in UTC

Returns the current real-world time as an ISO date string in UTC (Universal Time). Pair with .format_date(...) to display it. Returns the same shape of string as a date stored in a tracked item, so all the usual date tokens work.

now_local()

The current real-world time, in the player's local zone

Returns the current real-world time in the player's local timezone (captured from their browser). Pair with .format_date(...) to display it. If the player's timezone isn't known to the server, falls back to UTC.

sin(x)

Sine of x (radians)

Standard trigonometric sine. The angle is in radians, so a quarter turn is 1.5708 (π/2) and a full turn is 6.2832 (2π). Also available as a chain form: $angle.sin().

cos(x)

Cosine of x (radians)

Standard trigonometric cosine. The angle is in radians, so a half turn is 3.1416 (π) and a full turn is 6.2832 (2π). Also available as a chain form: $angle.cos().

tan(x)

Tangent of x (radians)

Standard trigonometric tangent. The angle is in radians. Also available as a chain form: $angle.tan().

exp(x)

e raised to the power of x

Returns the mathematical constant e (~2.718) raised to x. Mostly useful for advanced numeric formulas. Also available as a chain form: $x.exp().

power(x, n)

Raise x to the power of n

Returns x raised to the power of n. Equivalent to the ^ operator — power(4, 2) and 4^2 both give 16. Also available as a chain form: $x.power(n).

abs(x)

The positive version of a number

Strip a minus sign from a number. Useful when you only care about the size of a change, not its direction. Also available as a chain form: $change.abs().

round_down(x)

Round a number down to the nearest whole number

Round towards negative infinity. 3.9 becomes 3; -1.2 becomes -2. Also available as a chain form: $value.round_down().

round_down(x, decimals)

Round a number down to N decimal places

Round towards negative infinity at the given decimal place. round_down(3.78, 1) keeps one decimal and gives 3.7.

round_up(x)

Round a number up to the nearest whole number

Round towards positive infinity. 3.1 becomes 4; -1.8 becomes -1. Also available as a chain form: $value.round_up().

round_up(x, decimals)

Round a number up to N decimal places

Round towards positive infinity at the given decimal place. round_up(3.12, 1) keeps one decimal and gives 3.2.

Strings

characters()

Split text into single characters

Break a piece of text into a list of one-character pieces, including spaces and punctuation.

ends_with(text)

True if the text ends with this

Check whether a piece of text ends with the given suffix. Returns "true" or "false". Case-sensitive.

lines()

Split text into a list of lines

Break text on newline characters into a list of lines.

lower()

Convert text to lowercase

Make every letter lowercase. Useful before comparisons that should ignore case.

replace(old, new)

Replace one piece of text with another

Find every occurrence of old in the text and swap it for new. Case-sensitive.

split(sep)

Split text on a separator

Cut the text into pieces wherever the separator appears, and return them as a list.

starts_with(text)

True if the text starts with this

Check whether a piece of text begins with the given prefix. Returns "true" or "false". Case-sensitive.

title_case()

Capitalise The First Letter Of Each Word

Capitalise the first letter of each word, lowercase the rest.

trim()

Remove leading and trailing whitespace

Strip spaces, tabs and newlines from both ends of the text. Inner whitespace is left alone.

truncate(length)

Limit text to N characters

If the text is longer than N, cut it down to N characters. Texts shorter than N are returned unchanged. Nothing is appended unless you pass a suffix — see truncate(length, suffix).

truncate(length, suffix)

Limit to N characters and append a suffix

Like truncate(length), but append the suffix (e.g. "…") when the text was actually shortened. Short text is returned unchanged, with no suffix added.

upper()

Convert text to UPPERCASE

Make every letter uppercase. Pairs nicely with markdown like .bold() for emphasis.

words()

Split text into a list of words

Break text into words on word boundaries — punctuation is dropped, hyphens split words, apostrophes stay put inside contractions.

Numbers

abs()

The positive version of the value

Strip a minus sign from the value. (-5).abs() becomes 5.

constrain(min, max)

Keep a number between two bounds

Clamp the value: anything below min becomes min, anything above max becomes max, otherwise return the value unchanged.

cos()

Cosine of the value (radians)

Standard trigonometric cosine. The value is in radians.

exp()

e raised to the power of the value

The mathematical constant e (~2.718) raised to the value.

format_number()

Format a number with thousands commas

Insert commas every three digits, no decimals.

format_number(decimals)

Format with thousands commas and N decimal places

Insert commas every three digits and keep N decimal places.

format_percent()

Format a number as a percentage

Multiply by 100 and add a percent sign, no decimals. 0.85"85%".

format_percent(decimals)

Format a number as a percentage with N decimal places

Multiply by 100, add a percent sign, and round to the given number of decimal places. 0.8567 with 2 decimals → "85.67%".

ordinal()

Add 1st, 2nd, 3rd, … to a number

Format a whole number with its ordinal suffix in English.

power(n)

Raise the value to the power of n

Multiply the value by itself n times. (2).power(10) is 1024. Equivalent to the ^ operator: $x.power(2) is $x ^ 2.

round()

Round to the nearest whole number

Standard rounding (.5 rounds up).

round(decimals)

Round to N decimal places

Round to N digits after the decimal point.

round_down()

Round down to the previous whole number

Round towards negative infinity. 3.9 becomes 3; -1.2 becomes -2.

round_down(decimals)

Round down to N decimal places

Round towards negative infinity at the given decimal place.

round_up()

Round up to the next whole number

Round towards positive infinity. 3.1 becomes 4; -1.8 becomes -1.

round_up(decimals)

Round up to N decimal places

Round towards positive infinity at the given decimal place.

sin()

Sine of the value (radians)

Standard trigonometric sine. The value is in radians, so a quarter turn is 1.5708 (π/2).

tan()

Tangent of the value (radians)

Standard trigonometric tangent. The value is in radians.

Lists

current()

The item being updated, in a List Update effect

Only for the **List Update** effect on a YAML tracked item. As the update runs down the list, current() is the member being worked on — so current().hp is this member's hp. Use it in the **Target** to say which field to change, and in the **Value** to read this member's own fields (for example, heal everyone to full with Target current().hp and Value current().max_hp). It has no meaning anywhere else — the Playground, conditions, or Path Update — and reports an error if used there.

all()

Every child item

Return all the child items. The same as .where() with a condition that's always true, but it moves the content to the children rather than a property of the current context — which is what lets $party.all().name reach each member's name field.

any(condition)

True if at least one item matches

Test each item against the condition; return "true" if at least one matches, otherwise "false". The condition works just like the one in .where(): refer to fields directly and use operators such as =, <, >, contains, in, and and/or/not.

average()

The average of the numbers in a list

Add up every value and divide by the count. Works on a list of plain numbers; for a list of objects, use .average(field).

average(field)

The average of one field across all items

Pick out a single field on every item and average those numbers. Equivalent to .all().{field}.average() but shorter.

bulleted_list()

Format the list as bullet points

Join the items into one string, each on its own line and prefixed with a dash. Pairs nicely with .format_each() to customise each row.

contains(value)

True if the list contains this value

Check whether the value is in the list. Returns "true" or "false". On a map, this checks the keys.

in(collection)

True if the collection contains this value (the reverse of contains)

The mirror image of .contains() — check whether THIS value is in the given collection, reading subject-first. $skill.in($unlocked) is the same as $unlocked.contains($skill). Works on lists (element), maps (key), and text (substring). Returns "true" or "false".

count()

How many items are in the list

Return the number of items in the list (or entries in a map).

descendants()

Every nested item, flattened out

Walk a tree of nested objects and produce one big flat list of all the inner objects. Useful for skill trees, dialogue trees, or anything else with branching structure.

every(condition)

True if every item matches

Test each item; return "true" only if all of them match. Returns "true" for an empty list (nothing fails). The condition works just like the one in .where(): refer to fields directly and use operators such as =, <, >, contains, in, and and/or/not.

first()

The first item in the list

Return item number 1. Same as .item(1) but shorter to read.

first(n)

The first n items

Return up to the first n items as a new list. If the list is shorter than n, you get the whole list back.

flatten()

Combine nested lists into one

Take a list of lists and flatten one level so it becomes a single list. Doesn't dig deeper than one level. You rarely need it after a selection — a field read over .where(...)/.all() already gives one flat list of all the values.

format_each(template)

Apply a template to each item

Render a template string once per item, then return a list of the rendered strings. Inside the template, fields of the item are available directly by name (case-sensitive — {XP} and {xp} are different), and you can use index() for the current item's position (1-based). A bare name reaches only the item's own fields; to read an outer tracked item, prefix it with $ (e.g. {$gold}).

item(n)

The nth item in the list (1-based)

Pick out a single item by its position. Position 1 is the first item, position 2 the second, and so on. On a map, .item(...) takes a key instead — see .item(key) under Maps.

join(sep)

Glue items together with a separator

Combine every item into one piece of text, separated by the given string.

join(sep, final_sep)

Glue items, with a different last separator

Like .join(sep) but use a different separator before the last item — handy for natural-language lists.

last()

The last item in the list

Return the final item. Same as .item(N) where N is the count.

last(n)

The last n items

Return the final n items as a new list.

max()

The largest value in the list

Return the highest value in a list of plain numbers (or the one that sorts last alphabetically in a list of strings). For the largest *value of a field* across a list of objects, use .max(field). For the *item* with the largest field value, use .max_by(field).

max(field)

The largest value of a field across items

Evaluate field (or any per-item expression) on each item and return the largest result as a number. Returns a *value*, not an item — use .max_by(field) if you want the item itself.

max_by(field)

The item with the largest field value

Evaluate field on each item and return the *item itself* whose field value is largest. Different from .max(field), which returns just the value. Chain .max_by(field).<field> to get back to the value if needed. Always a single item — if several tie for the largest value, you get the first of them in list order. To act on ALL tied items, filter instead: $party.where(level = $party.max(level)).

min()

The smallest value in the list

Return the lowest value in a list of plain numbers (or the one that sorts first alphabetically in a list of strings). For the smallest *value of a field*, use .min(field). For the *item* with the smallest field value, use .min_by(field).

min(field)

The smallest value of a field across items

Evaluate field (or any per-item expression) on each item and return the smallest result as a number. Returns a *value*, not an item — use .min_by(field) if you want the item itself.

min_by(field)

The item with the smallest field value

Evaluate field on each item and return the *item itself* whose field value is smallest. Different from .min(field), which returns just the value. Always a single item — if several tie for the smallest value, you get the first of them in list order. To act on ALL tied items, filter instead: $party.where(hp = $party.min(hp)).

none(condition)

True if no items match

Test each item; return "true" only if none of them match. Equivalent to .any(condition).not(). The condition works just like the one in .where(): refer to fields directly and use operators such as =, <, >, contains, in, and and/or/not.

numbered_list()

Format the list as a numbered list

Join the items into one string, each on its own line, numbered from 1.

plain_list()

Join items with newlines, no prefix

Like .bulleted_list() and .numbered_list() but with nothing in front of each line — just the items separated by newlines.

random()

One random item from the list

Pick one item at random. Each call may return a different item. Different from the top-level random() which gives a number.

random(n)

n random items from the list

Pick n items at random, without repeats. If n is bigger than the list, you get the whole list shuffled.

reverse()

Reverse the order of the list

Return the items in opposite order. Often paired with .sort() for descending order, though .sort_by(field, desc) is clearer when sorting by a field.

shuffle()

Randomise the order of the list

Return the items in a random order. Useful for picking a rotating subset, or for pseudo-random adventure flavour.

sort()

Sort the list (ascending)

Sort numerically or alphabetically, smallest/A first. For objects with multiple fields, use .sort_by(field) instead. Capital letters sort before lowercase.

sort_by(field)

Sort the list by one field (ascending)

Sort a list of objects by the value of one field. The field name isn't quoted — just write it like a path. Items missing the field sort to the end.

sort_by(field, desc)

Sort by one field (descending with `desc`)

Same as .sort_by(field) but flip the order with the bare word desc. Use the bare word asc for ascending if you like being explicit.

sum()

Add up all the numbers

Total up the items in a numeric list. For a list of objects, use .sum(field).

sum(field)

Add up one field across all items

Total a single field across every item. You can also use a short calculation in place of a field name (level + bonus).

then_by(field)

A tie-breaker for sort_by (ascending)

Comes after .sort_by(...) (or another .then_by(...)) and breaks ties using a second field.

then_by(field, desc)

A tie-breaker for sort_by (descending)

Same as .then_by(field) with the order flipped.

union(other)

Join two lists into one

A new list holding this list's items followed by the other's. Duplicates are KEPT (an inventory with three potions has three potions) — chain .unique() when you want each value once. The other set questions are filters: .where(value() in $other) keeps the items both lists share, and .where(value() not in $other) keeps the items the other list doesn't have.

unique()

Remove duplicate items

Return a new list with each value appearing only once. Order is preserved (first occurrence wins).

where(condition)

Keep only the items that match a test

Test each item against the condition; keep the ones that match, drop the rest. Inside the condition, fields are available directly, and you can use =, <, >, contains, in, etc.

Maps

exists()

True if the path leads somewhere

Check whether the chain you've just walked through actually points at something — useful for guarding against missing fields or empty maps. Returns "true" or "false".

item(key)

A map entry by its key — with an optional fallback

Pick a map entry by key. The key can be a quoted string or any expression ($favourite), which is how you reach an entry whose name is stored in data. A second value is a READ fallback, returned when the key isn't there — the safe-tally idiom for keys that may not exist yet. In a script, .item(key) also works as an assignment target (the fallback belongs on the right-hand side: $rep.item($f) = $rep.item($f, 0) + 5). For a key you already know, a quoted field is usually tidier — $names_list."My Name" reads the same entry as .item("My Name"). Keep .item() for a key held in a variable or when you want a fallback.

key()

The key of a single map entry

When you've narrowed a map down to one entry (e.g. with .first() or .where(...)), this gives you that entry's key. Inside .format_each() it gives the current entry's key.

keys()

A list of the map's keys

Get every key from a map (or the keys of the map entries that survived a .where() filter).

value()

The value of a single map entry, or the current item itself

Mirror of .key() — when you've narrowed a map to one entry, this returns its value. Inside .where() and .format_each() it is the current entry's value — and when the items are plain values rather than blocks of fields, it is the item itself. That's how you test a plain item in a filter, since bare names only reach fields.

values()

A list of the map's values

Get every value from a map, dropping the keys.

Scripts

append(value)scripts only

Add a value to the end of a list (script statement)

In a "Run a script" effect, add a value to the end of a list. Given a whole list (or a .where() selection), each of its values is appended, so the result stays flat. Script only — expressions can't change values, so this never appears inside <<…>>.

remove(value)scripts only

Remove from a list or map (script statement)

In a "Run a script" effect: on a MAP, removes the entry with the given key; on a LIST, removes every item equal to the value. With NO value, .remove() closes a selection — select, then remove: $party.where(hp <= 0).remove() deletes exactly the selected slots, and on a map .where(...) selects entries by key() / value() conditions. Script only.

log(message, level)scripts only

Leave a note for the world designer (script statement)

Record a message for YOU, the designer — players never see it. Messages appear in the playground's Log section and in World Debug's PawScript Log. A quoted message works like format_text(...) ({…} slots evaluate); any other value logs as-is. The optional level is "info" (default) or "error". Messages logged before a failed run still arrive — they're your key diagnostic exactly then.

index()

The current item's position (1-based)

On a for each loop variable ($m.index()) — and inside .format_each(...) templates — the position of the current item, starting at 1. Handy for ranks and numbered lines.

is_first()

True for the first item

On a for each loop variable — and inside .format_each(...) templates — "true" when the current item is the first one.

is_last()

True for the last item

On a for each loop variable — and inside .format_each(...) templates — "true" when the current item is the last one, e.g. to treat the final entry differently.

Markdown

bold()

Wrap the text so it renders **bold**

Surround the text with markdown bold markers. The renderer will display it as bold; outside markdown contexts you'll see the asterisks.

code()

Wrap the text so it renders as `code`

Surround the text with markdown backticks for inline code.

italic()

Wrap the text so it renders *italic*

Surround the text with markdown italic markers.

strikethrough()

Wrap the text so it renders ~~struck through~~

Surround the text with markdown strikethrough markers — useful for crossed-out items in a quest log.

Other

format_date(pattern)

Format a date with English-word tokens

Format a date string using friendly tokens like day, monthname and year. Anything in the pattern that isn't a token is kept as-is, so spaces, commas, slashes and dashes all work as separators.

The tokens (showing what each produces for Wednesday 8 April 2026, 2:07:08 PM):

- day / day2 — day of month (8 / 08) - month / month2 — month number (4 / 04) - monthname / monthname3 — month name (April / Apr) - year / year2 — year (2026 / 26) - weekday / weekday3 / weekday2 / weekday1 — day of week (Wednesday / Wed / We / W) - hour / hour2 — 12-hour clock (2 / 02), pair with an AM/PM marker - hour24 — 24-hour clock, 2 digits (14) - minute / minute2 — minute (7 / 07) - second / second2 — second (8 / 08) - AMPM / ampm — AM/PM marker, upper- or lower-case (PM / pm)

not()

Flip true to false (and vice versa)

Logical NOT. Useful after a test that returns "true" / "false" when you want the opposite.