Collections
A collection is data packed when the project builds: a list or a map you declared, or a table the game ships. A container is the server’s, and walks like one. See Declaring your own data and Rendering lists.
List<Value> positions 0, 1, 2 … to valuesMap<Key, Value> keys to values, in the order declaredContainer the items in one of the server's containers, by slotNone of them can be written to while the game runs. A list and a map are fixed when the project builds; a container changes only when the server changes it.
Reference
Section titled “Reference”List<Value>
Section titled “List<Value>”Declared with listOf, or read from the game through a
ListRef. Positions count from zero.
list.at(i) | The value at position i. Value when i is a literal, a const bound to one or an enum member; Value | null otherwise |
list.length | How many entries the list holds. Read while the interface runs |
list.map((value, i) => …) | One component per entry, in order. i is the position |
list.scan((value, i, running) => …, by, from?) | map carrying a running total. See scan |
list.filter((value, i) => …) | The entries the comparison keeps, for a map or a scan after it. i is the position in this list |
filter and the map after it both hand you a position, and they are
different numbers. filter’s is the position in the list it was called on,
before anything was dropped. map’s is the position among what survived, so
a filtered strip closes up when it is placed by map’s index.
{HAULS .filter((haul, i) => i % 2 === 0) .map((haul, i) => ( <sprite x={i * 40} y={0} w={36} h={32} item={haul.item} itemCount={-1} /> ))}Map<Key, Value>
Section titled “Map<Key, Value>”Declared with mapOf, or read from the game through a
MapRef. A key is a whole number or a kind of game
thing — an Item, a Skill, an enum member — never text.
map.get(key) | The value under key. Value when key is a literal, a const bound to one or an enum member; Value | null otherwise |
map.at(i) | The value at position i in declared order. Nullable by the same rule |
map.length | How many entries the map holds. Read while the interface runs |
map.map((value, key, i) => …) | One component per entry, in declared order. key is the entry’s own key, i its visible position |
map.scan((value, key, i, running) => …, by, from?) | map carrying a running total. See scan |
map.filter((value, key) => …) | The entries the comparison keeps. There is no position parameter |
{CategoryNames.map((label, category, i) => ( <text x={i * 76} y={0} w={72} h={14} text={label} font={Fonts.SMALL} valign="middle" shadow color={category === Category.TOOLS ? Colors.WHITE : Colors.DIM} />))}A map you declared iterates in the order its pairs were written. A table the game ships iterates in whatever order the game stored it.
A read that can miss
Section titled “A read that can miss”at and get answer Value | null unless the index or key is a constant
the type checker can see: a literal, a const bound to one, or an enum
member.
- A number or a string:
?? fallbacknames what a miss reads as. - A record:
if (row === null) { return null; }— there is no empty row to substitute.
?? also fires on a stored empty value — -1 for a number, "" for text — so
choose a fallback the data cannot hold. Types
has the rule; Declaring your own data
shows it on screen.
scan: a walk with a running total
Section titled “scan: a walk with a running total”scan is map with a number threaded from entry to entry. by says how much
this entry adds; from is where the total starts, and defaults to 0. The
callback’s last parameter is the total as this entry begins — the sum of
every earlier entry.
{WARES.scan( (ware, i, y) => <WareRow ware={ware} coins={coins} y={y} />, (ware) => rowHeight(ware), 4,)}Several totals in one walk. Give by a list of callbacks and from a list
of starting values, one per total. The callback names the totals last, in the
order the by list declares them:
{HAULS.scan( (haul, i, y, before) => <HaulRow haul={haul} y={y} before={before} />, [(haul) => rowHeight(haul), (haul) => haul.value], [4, 0],)}Rules:
- A
bymay read the totals too, after its own parameters and in the same order, and may stop early. Everybysees the totals as the entry began, its own included. - An entry
filterdropped adds nothing. - Nothing after the walk can read the final total.
scanplaces rows; it is not a sum.
A scan over a constant array is refused (TS2E227). Write the total into the array, or declare a collection.
listOf and mapOf
Section titled “listOf and mapOf”const PRAYERS = listOf<Prayer>([ { name: 'Thick Skin', level: 1, icon: PrayerIcons.THICK_SKIN }, { name: 'Burst of Strength', level: 4, icon: PrayerIcons.BURST_OF_STRENGTH },const CategoryNames = mapOf<Category, String>([ [Category.TOOLS, 'Tools'], [Category.CARE, 'Plant care'],]);listOf<Value>([…]) | Declares a List<Value>, packed into the game’s data when the project builds |
mapOf<Key, Value>([[key, value], …]) | Declares a Map<Key, Value>. Entries are pairs |
Both are declared at module level, and every entry is written out in place.
Records as row types
Section titled “Records as row types”A collection’s Value may be a record: a type with named fields. Declare the
type, and every entry is an object with those fields:
type Haul = { item: Item; name: String; value: Int; note?: String;};A record’s fields are ordinary properties, typed as declared, and a record is an
ordinary prop type. A field may be optional; an entry that leaves it out reads
it back as the type’s empty value — -1 for a number, "" for text — so the
read is haul.note ?? '', and the test is on what came back:
const rowHeight = (haul: Haul): Int => (haul.note ?? '').length > 0 ? 38 : 26;The record type must be named — listOf<Haul>, not an inline { … } — and a
record is not a value on its own: pass it whole to a prop or read one of its
fields.
ListRef and MapRef
Section titled “ListRef and MapRef”What the generated dictionaries declare for the game’s own tables. You alias
them and read them as a List or a Map:
export declare const SkillIcons: typeof GameEnums.ENUM_255;const icon = SkillIcons.get(Skills.WOODCUTTING);| Reads as | Carries | |
|---|---|---|
ListRef<Id, Length, Value> | List<Value> | The table’s id, how many entries it has, and what type they are. Only a table whose keys run from 0 is a list |
MapRef<Id, Rows, Key, Value, FirstKey> | Map<Key, Value> | The table’s id, its row count, the types of its keys and values, and the key its rows start at |
A table of rows — Value of 'struct' — reads as a collection of
Rows, whose fields are read
with .int(), .text() and .sprite().
Container
Section titled “Container”What useInventory returns.
inv[slot] | The item in a slot, or -1 when it is empty |
inv.count(slot) | How many of the item in a slot |
inv.total(item) | How many of an item across every slot |
inv.size | How many slots the container has |
inv.param(slot, ref) | A parameter of the item in a slot, as a whole number, typed by the parameter’s kind |
inv.paramText(slot, ref) | A text parameter of the item in a slot |
inv.paramSprite(slot, ref) | A sprite parameter of the item in a slot |
inv.map((item, slot) => …) | One component per slot, every slot visited. slot is the slot |
inv.scan((item, slot, running) => …, by, from?) | The same walk with a running total |
inv.filter((item, slot) => …) | The slots the comparison keeps. In the map after it the second parameter counts what survived, not the slot |
{inventory.map((item, slot) => ( <Slot item={item} count={inventory.count(slot)} slot={slot} />))}<text x={0} y={0} w="fill" h={14} font={Fonts.SMALL} valign="middle" shadow color={Colors.DIM} text={`${inventory.size} slots, ${inventory.count(0)} in the first`} /><text x={0} y={15} w="fill" h={14} font={Fonts.SMALL} valign="middle" shadow color={Colors.DIM} text={`The first slot needs level ${inventory.param(0, AxeParams.LEVEL_REQUIRED)}`} />A parameter read is never null: every parameter carries a default.
param on an item you hold rather than a slot is on
Reading game data.
Arrays the game hands you
Section titled “Arrays the game hands you”A function the server calls may receive an array — Ints<T> of whole numbers
(or of any kind of game thing, Ints<Item>), or Strings of text. Walk it
with while, reading ids[i] and ids.length; .map() over one is refused
(TS2E254), and so is a store into one (TS2E253).
intArray(size) makes an array of your own inside a function — intArray<Item>(18)
for items, with an optional list of initial values — which .map() can walk.
These are for entry points and per-row bookkeeping, not for interface data; a
list or a map is.
Iterating what you declared
Section titled “Iterating what you declared”{PRAYERS.map((prayer, i) => ( <PrayerCell prayer={prayer} unlocked={prayer.level <= level} x={(i % COLUMNS) * CELL} y={(i / COLUMNS) * CELL} />))}{PRAYERS .filter((prayer) => prayer.level <= STARTING_LEVEL) .map((prayer, i) => ( <sprite x={92 + i * 20} y="center" w={18} h={18} sprite={prayer.icon} /> ))}Passing a collection around
Section titled “Passing a collection around”A collection is an ordinary value: a local, a parameter, a return value, a prop, or another map’s value type.
const categoryLabel = (names: Map<Category, String>, ware: Ware): String => names.get(ware.category) ?? 'Sundries';Reading single values from a collection you were handed works anywhere.
Iterating one does not: iterate a collection by the name it was declared under; read values from one you
were handed. The other shape is refused (TS2E212):
const Rows = ({ names }: { names: List<String> }): Component => ( <layer w="fill" h="fill"> {names.map((name, i) => ( // refused <text x={0} y={i * 16} w="fill" h={16} text={name} font={Fonts.PLAIN} color={Colors.WHITE} /> ))} </layer>);Reading the container’s own facts
Section titled “Reading the container’s own facts”const inventory = useInventory(Inventories.INVENTORY);const coins = inventory.total(Items.COINS);Troubleshooting
Section titled “Troubleshooting””Object is possibly ‘null’”
Section titled “”Object is possibly ‘null’””You read with an index or key that is not a constant. Add ?? fallback for a
number or a string; test === null for a record. See
A read that can miss.
My filtered rows have gaps in them
Section titled “My filtered rows have gaps in them”You placed the rows by filter’s index, which counts the unfiltered list. Place
them by the index the map after it receives — that one counts what survived.
The build warns that my component rebuilds
Section titled “The build warns that my component rebuilds”A filter whose comparison reads state raises TS2E251. Move the test
inside the row. Rendering lists
shows both shapes.
My map iterates in a strange order
Section titled “My map iterates in a strange order”A table the game ships iterates in the order the game stored it. Declare the
data with mapOf when the order is yours to choose.
A fixed-size array for one call of one function is its own page: intArray.