Appearance
Loops, Arrays, Numbers & Strings
Iterating over collections and engine namespaces, and the value macros for working with arrays, numbers, and text.
Loops & Namespace Queries
Ana provides a loop construct and three collection macros ((ids:), (filter:), and (query:)) that work across all engine namespaces: $npc, $inv, $container, and any custom namespace registered with (ns-nested:). These macros always return arrays, so they pair naturally with (each:).
All three macros return arrays you can iterate with (each:).
each
(each: _var in collection)[body]. Loops over every item in an array, binding the current item to a temp variable. The block renders once per iteration.
ana
(set: _items to (ids: $inv))
(each: _id in _items)[
(get: $item, _id, "name")
]The collection can be any expression that returns an array: a variable, a filter result, or any other expression.
Aliases: (for:)
break
(break:) stops the enclosing (each:)/(for:) loop immediately. See (continue:) for the companion. Both affect only the innermost enclosing loop, work inside (if:) branches within the loop, and are ignored outside a loop (with a dev-build warning and a validator warning).
continue
(continue:) skips the rest of the current loop iteration and moves to the next. See (break:).
ana
// First weapon in a large inventory: stop as soon as we find one
(each: _id in (ids: $inv))[
(if: (get: $item, _id, "type") is "weapon")[
You ready your (get: $item, _id, "name").
(set: $world.drawnWeapon to _id)
(break:)
]
]
// Skip locked entries
(each: _id in (ids: $container.chest))[
(if: (get: $item, _id, "locked"))[(continue:)]
You take the (get: $item, _id, "name").
]ids
(ids: namespace). Expression macro. Returns an array of all IDs in the given namespace.
ana
(ids: $npc) // all NPC IDs
(ids: $inv) // all item IDs in carry inventory
(ids: $container.chest) // all item IDs in a specific container
(ids: $faction) // all IDs in a custom nested namespacefilter
(filter: ns.key is value [, ns.key2 is value2 ...]). Expression macro. Returns an array of IDs where all predicates match (AND logic). Multiple predicates are supported for 2-part ns.key forms.
ana
// NPC filter
(set: _atBar to (filter: $npc.location is "bar"))
(each: _id in _atBar)[
(get: $npc, _id, "name") nurses a drink.
]
// Multi-predicate AND
(set: _targets to (filter: $npc.location is "bar", $npc.gender is "female"))
// Inventory filter: returns item IDs matching a property
(set: _weapons to (filter: $inv.type is "weapon"))
(each: _id in _weapons)[
You're carrying a (get: $item, _id, "name").
]
// Multi-predicate inventory filter
(set: _heavy to (filter: $inv.type is "weapon", $inv.weight > 5))
// Container filter: 3-part form ($container.id.key), single predicate only
(set: _potions to (filter: $container.shop_goods.type is "potion"))
// Custom namespace
(set: _friendly to (filter: $faction.standing > 50))Filter operators: is, is not, >, <, >=, <=.
npc.locationhere is a predicate, not a variable.$npcis a nested namespace keyed by id; the actual stored values live at$npc.bartender.location,$npc.guard.location, and so on. Thenpc.location is "bar"form inside(filter:)/(query:)means "for every npc id, test itslocationkey"; it does not read a single$npc.locationvalue (there is no such variable). This is how you iterate a nested namespace:(filter:)/(query:)walk every id for you, or use(each: _id in (ids: $npc))and read each with(get: $npc, _id, "location").
query
(query: namespace [, where, ns.key is value] [, sort, "key" [desc]] [, select, "key"] [, distinct] [, limit, N]). Expression macro. A composable query with optional filtering, sorting, projection, and a result cap. Returns an array of IDs (or, with select, an array of property values).
The first argument is the namespace. where predicates use the same form as (filter:). The pipeline runs in order: where → sort → select → distinct → limit.
select, "key": projects each matching entry to that property's value instead of returning its ID. Works across flat namespaces ($inv,$container.id), nested namespaces ($npc), and any custom namespace registered with(ns-nested:).distinct: drops duplicate results (first occurrence kept). Most useful withselect.
ana
// All NPCs in the bar, sorted by name
(set: _bar to (query: $npc, where, $npc.location is "bar", sort, "name"))
// Top 3 highest-level hostile NPCs
(set: _enemies to (query: $npc, where, $npc.faction is "hostile", sort, "level", desc, limit, 3))
// Inventory: highest-damage weapons, cap 5
(set: _top to (query: $inv, where, $inv.type is "weapon", sort, "damage", desc, limit, 5))
// Distinct item types present in inventory: ideal for building tabs
(set: _types to (query: $inv, select, "type", distinct)) // e.g. ["weapon", "consumable"]
// Distinct locations NPCs are currently in
(set: _places to (query: $npc, select, "location", distinct))ns-nested (GameInit only)
(ns-nested: "name") registers a new nested namespace that behaves identically to npc: it supports (declare: $name.id, ...), (ids: name), (filter: name.key is value), and (query: name, ...).
ana
(ns-nested: "faction")
(declare: $faction.guild, name, "Merchant Guild", standing, 50)
(ids: $faction) // ["guild"]
(filter: $faction.standing > 40) // ["guild"]
(query: $faction, where, $faction.standing > 40, sort, "name")Note: there is no
(ns-flat:)macro. Flat namespaces ($world.timeOfDay,$player.gold) work automatically the moment you(declare:)a variable in them; no registration call is needed. See Variable depth for the three namespace depths.
Quick reference by namespace
| Namespace | (ids:) | (filter:) predicate | Notes |
|---|---|---|---|
npc | (ids: $npc) | $npc.key is value | Any (npc-define:) / (declare: $npc.id, ...) entry |
| inventory | (ids: $inv) | $inv.key is value | Item template IDs |
| container | (ids: $container.chest) | $container.chest.key is value | 3-part, single predicate |
| custom | (ids: $faction) | $faction.key is value | After (ns-nested: "faction") |
Events are plain array variables; loop directly with (each: _ev in $npc.bartender.events)[...]
Arrays
Arrays are declared with (declare: $variable, []) in GameInit. Use arrays for ordered lists of strings or numbers: visited locations, collected clues, event flags.
add
(add: $arrayVariable, value) appends a value to the end of an array. This is the same (add:) macro used for numeric variables; it detects the array type automatically. To remove a value use (remove: $arr, value), and (count: $arr) returns an array's length; there are no separate arr-* mutation macros.
ana
(declare: $player.events, [])
(add: $player.events, "met_kyle") // append
(remove: $player.events, "met_kyle") // remove first occurrence
(if: (count: $player.events) >= 5)[You've been around.]contains / not contains / does not contain operators
Built into the condition syntax. Checks whether an array or string includes a value. No macro needed.
ana
(if: $visited contains "mill")[
You know the layout.
]
(if: $visited not contains "mill")[
You've never been out there.
]
(if: $visited does not contain "mill")[
You've never been out there.
]Event flags via arrays: declare an event array, push tags to it, check with contains:
ana
// GameInit
(declare: $player.events, [])
(add: $player.events, "found_note")
(if: $player.events contains "found_note")[
You remember that report.
]Traits: just a string array on the character namespace:
ana
// GameInit
(declare: $player.traits, [])
(add: $player.traits, "charming")
(if: $player.traits contains "charming")[
Your smile puts them at ease.
]
(if: $player.traits does not contain "fearless")[
Something gives you pause.
]Array Macros
Functional array operations; all return new arrays or single values. None of these mutate the original variable. For array mutation (append, remove), use (add:) and (remove:) (see Arrays above).
All are expression macros.
arr
(arr: val1, val2, ...) builds a new array from a series of inline values. (arr:) with no arguments returns an empty array.
ana
(set: $player.inventory to (arr: "sword", "shield", "potion"))
(set: _empty to (arr:))pick
Returns a random value. Pass a single array to pick one of its elements, or a series of inline values to pick among them. Returns null for an empty array or empty series.
ana
(pick: $array)
(pick: val1, val2, ...)
(set: _greeting to (pick: $npc.bartender.greetings)) // from an array
(pick: "He nods.", "He shrugs.", "He grunts.") // from inline valuesAliases: (either:) reads naturally for one-off random prose where no array variable is needed.
weighted
(weighted: value, weight, value, weight, ...) returns one value chosen at random, each with probability proportional to its weight. This is a weighted random table for loot drops, random encounters, or any roll where outcomes aren't equally likely. Weights are any positive numbers and need not sum to 100. A zero or negative weight is never chosen, and the macro returns null when no pair has a positive weight.
ana
(set: _drop to (weighted: "common", 70, "rare", 25, "legendary", 5))
// Weights can be variables, so odds can shift with game state:
(set: _encounter to (weighted: "wolf", 50, "bear", $world.danger, "nothing", 30))first
(first: $array) returns the first element, or null for an empty array.
ana
(set: _opening to (first: $player.events))last
(last: $array) returns the last element, or null for an empty array.
ana
(set: _latest to (last: $player.events))nth
(nth: $array, n) returns the element at 1-indexed position n. Returns null if out of range.
ana
(set: _second to (nth: $player.events, 2))shuffle
Returns a new randomly shuffled copy. Accepts either a single array argument or a series of inline values. The original is unchanged.
ana
(shuffle: $array)
(shuffle: val1, val2, ...)
(set: _deck to (shuffle: $game.deck))sort
Returns a new sorted copy in ascending order: numbers numerically, strings lexicographically. Accepts either a single array argument or a series of inline values. The original is unchanged.
ana
(sort: $array)
(sort: val1, val2, ...)
(set: _ranked to (sort: $player.scores)) // array form
(set: _ranked to (sort: 3, 1, 2)) // inline series → [1, 2, 3]reverse
Returns a reversed copy of an array, or a reversed string. The original is unchanged.
ana
(reverse: $array)
(reverse: string)
(set: _reversed to (reverse: $player.inventory))
(set: _flip to (reverse: "Steve")) // → "evetS"unique
(unique: $array) returns a new copy with duplicate values removed, preserving first occurrence order.
ana
(set: _visited to (unique: $player.locations))Aliases: (dedupe:)
slice
Returns a portion of an array or a string from 1-indexed position from to to (inclusive). If to is omitted, slices to the end. The original is unchanged.
ana
(slice: $array, from, to?)
(slice: string, from, to?)
(set: _middle to (slice: $player.events, 2, 4))
(set: _tail to (slice: $player.events, (count: $player.events) - 2))
(set: _sub to (slice: "hello", 2)) // → "ello"range
Builds an array of consecutive integers from from to to, inclusive on both ends. Counts down instead of up when from is greater than to. Non-integer endpoints are truncated toward zero.
ana
(range: from, to)
(set: _nums to (range: 1, 10)) // → [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
(set: _down to (range: 5, 1)) // → [5, 4, 3, 2, 1]
(each: _i in (range: 1, 3))[Floor _i. ]Pairs naturally with (each:) for count-based loops. For repeating a string a fixed number of times, use (repeat:) instead.
length
Returns the character count of a string or the entry count of an array.
ana
(length: value)
(set: _chars to (length: $player.name)) // string → character count
(set: _items to (length: $player.inventory)) // array → entry countjoin
Concatenates strings, or merges arrays into one new array. If every argument is an array, the result is a merged array; otherwise the arguments are coerced to strings and concatenated.
ana
(join: val1, val2, ...)
(set: _full to (join: "Hello, ", $player.name, "!")) // strings → one string
(set: _all to (join: $party, $reserves)) // arrays → one merged arrayAliases: (concat:)
This is distinct from (collapse:), which joins the elements of a single array into a string with a separator.
Number Macros
Utility math functions. All are expression macros; use them inside (set:), conditions, or anywhere a value is expected.
min
(min: a, b) returns the smaller of two numbers.
ana
(set: _capped to (min: $player.health, 100))max
(max: a, b) returns the larger of two numbers.
ana
(if: (max: $rel.bartender, 0) > 50)[
He's friendly.
]clamp
(clamp: value, min, max) constrains a value to the range [min, max]. Returns min if below, max if above, otherwise value unchanged.
ana
(set: _safe to (clamp: $player.health, 0, 100))
(set: _vol to (clamp: _rawVol, 0.0, 1.0))round
(round: value) rounds to the nearest integer (0.5 rounds up).
ana
(set: _display to (round: $player.health))floor
(floor: value) rounds toward negative infinity.
ana
(set: _slots to (floor: $player.stamina / 10))ceil
(ceil: value) rounds toward positive infinity.
ana
(set: _pages to (ceil: $items / 10))abs
(abs: value) returns the absolute value of a number.
ana
(set: _diff to (abs: $player.health - $player.maxHealth))sum
(sum: ...) totals its numbers. Accepts either a single array argument or a series of inline numbers.
ana
(set: _total to (sum: $player.scores)) // array form
(set: _total to (sum: 10, 20, 30)) // inline → 60avg
(avg: ...) returns the mean of its numbers (0 for an empty series). Accepts either a single array argument or a series of inline numbers.
ana
(set: _mean to (avg: $player.scores))convert
(convert: value, fromUnit, toUnit) converts a numeric value between units in the same category. Supported categories:
- Length:
mm,cm,m,km,in,ft,yd,mi - Mass:
mg,g,kg,oz,lb,st - Volume:
ml,l,floz,cup,pt,qt,gal - Temperature:
c,f,k
Unit names are case-insensitive and accept common spellings (e.g. "feet", "pounds", "celsius"). Converting between incompatible categories throws an error.
ana
(set: _cm to (convert: 6, "ft", "cm")) // → 182.88
(set: _f to (convert: 100, "c", "f")) // → 212String Macros
All are expression macros. Use inside (set:) or anywhere a value is expected. To measure a string's length use (length:); to take a substring use (slice:); to reverse one use (reverse:); those macros work on both strings and arrays.
lowercase
(lowercase: str) converts a string to lower case.
ana
(set: _tag to (lowercase: $world.event))uppercase
(uppercase: str) converts a string to upper case.
ana
(set: _shout to (uppercase: $world.event))proper
(proper: str) capitalizes the first letter of each word.
ana
(set: _name to (proper: $player.name)) // "the old mill" → "The Old Mill"trim
(trim: str) strips leading and trailing whitespace.
ana
(set: _clean to (trim: _rawInput))split
(split: str, sep) splits a string into an array by the separator.
ana
(set: _parts to (split: "a,b,c", ","))
// → ["a", "b", "c"]
(each: _tag in (split: $player.tagString, " "))[
_tag
]collapse
(collapse: $array, sep?) collapses an array's elements down into one string, joined by sep. If sep is omitted, elements are concatenated with no separator. This is the inverse of (split:).
ana
(set: _arr to ["a","b","c"])
(set: _out to (collapse: _arr, "-")) // → "a-b-c"
(set: _out to (collapse: _arr)) // → "abc"(Not to be confused with (join:), which concatenates separate string/array arguments rather than the elements of one array.)
repeat
(repeat: count, value) returns a string with value repeated count times. count is floored and clamped to a minimum of 0.
ana
(set: _bar to (repeat: 10, "=")) // → "=========="
(set: _dots to (repeat: $level, "."))str
(str: value) converts any value to its string representation.
ana
(set: _label to (str: $player.gold) + " gold")num
(num: str) parses a string as a number, returning NaN if the string is not a valid number.
ana
(set: _parsed to (num: "3.14"))