Skip to content

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 values
Map<Key, Value> keys to values, in the order declared
Container the items in one of the server's containers, by slot

None 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.

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.lengthHow 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.

src/reference-language-collections.ts2
{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} />
))}

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.lengthHow 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
src/wares.ts2
{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.

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: ?? fallback names 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 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.

src/wares.ts2
{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:

src/reference-language-collections.ts2
{HAULS.scan(
(haul, i, y, before) => <HaulRow haul={haul} y={y} before={before} />,
[(haul) => rowHeight(haul), (haul) => haul.value],
[4, 0],
)}

Rules:

  • A by may read the totals too, after its own parameters and in the same order, and may stop early. Every by sees the totals as the entry began, its own included.
  • An entry filter dropped adds nothing.
  • Nothing after the walk can read the final total. scan places 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.

src/lists.ts2
const PRAYERS = listOf<Prayer>([
{ name: 'Thick Skin', level: 1, icon: PrayerIcons.THICK_SKIN },
{ name: 'Burst of Strength', level: 4, icon: PrayerIcons.BURST_OF_STRENGTH },
src/wares.ts2
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.

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:

src/reference-language-collections.ts2
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:

src/reference-language-collections.ts2
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.

What the generated dictionaries declare for the game’s own tables. You alias them and read them as a List or a Map:

src/refs.ts2
export declare const SkillIcons: typeof GameEnums.ENUM_255;
src/game-data.ts2
const icon = SkillIcons.get(Skills.WOODCUTTING);
Reads asCarries
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().

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.sizeHow 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
src/inventory.ts2
{inventory.map((item, slot) => (
<Slot item={item} count={inventory.count(slot)} slot={slot} />
))}
src/reference-language-collections.ts2
<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.

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.

src/lists.ts2
{PRAYERS.map((prayer, i) => (
<PrayerCell prayer={prayer} unlocked={prayer.level <= level}
x={(i % COLUMNS) * CELL} y={(i / COLUMNS) * CELL} />
))}
src/lists.ts2
{PRAYERS
.filter((prayer) => prayer.level <= STARTING_LEVEL)
.map((prayer, i) => (
<sprite x={92 + i * 20} y="center" w={18} h={18} sprite={prayer.icon} />
))}

A collection is an ordinary value: a local, a parameter, a return value, a prop, or another map’s value type.

src/wares.ts2
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):

refused/collections.ts2
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>
);
src/inventory.ts2
const inventory = useInventory(Inventories.INVENTORY);
const coins = inventory.total(Items.COINS);

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.

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.

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.