Skip to content

Data

Data your interface owns, such as a shop’s stock, is declared with listOf:

src/wares.ts2
const WARES = listOf<Ware>([
{ item: FarmingItems.RAKE, name: 'Rake', price: 12, category: Category.TOOLS, restock: 30 },
{ item: FarmingItems.SEED_DIBBER, name: 'Seed dibber', price: 6, category: Category.TOOLS, restock: 30 },
…
{ item: FarmingItems.BOTTOMLESS_COMPOST_BUCKET, name: 'Bottomless bucket', price: 5_440, category: Category.SEEDS },
]);

A plain array literal and an enum work too, for a fixed list. listOf and mapOf make a collection you can filter, look up and pass around.

A record type gives each entry named fields:

src/wares.ts2
type Ware = {
item: Item;
name: String;
price: Int;
category: Category;
/** How many the stall restocks to. Absent on the one-off pieces. */
restock?: Int;
};

Fields read as properties, ware.name, and a record is a valid prop type. An entry that leaves out an optional number field reads back -1:

src/wares.ts2
{(ware.restock ?? 0) > 0 && (
<text x={44} y={25} w={116} h={12} text={`restocks ${ware.restock}`}
font={Fonts.SMALL} valign="middle" shadow color={Colors.DIM} />
)}

A list is positional, read with .at(i). A map is keyed: declare it with mapOf and read it with .get(key):

src/wares.ts2
const CategoryNames = mapOf<Category, String>([
[Category.TOOLS, 'Tools'],
[Category.CARE, 'Plant care'],
]);

Entries are [key, value] pairs. A map iterates in the order its pairs were written; see Rendering lists.

.at(i) and .get(k) return Value | null unless the index or key is a literal. For a scalar, ?? gives the fallback:

src/wares.ts2
const categoryLabel = (names: Map<Category, String>, ware: Ware): String =>
names.get(ware.category) ?? 'Sundries';

For a record, compare against null:

src/wares.ts2
const ware = WARES.at(i);
if (ware === null) {
return null;
}

A collection can be bound, passed and returned like any value.

Every font, sprite, item, sound and animation the game ships has a generated name. Your project carries one file per kind under src/game/:

sprites.ts2 items.ts2 varps.ts2 animations.ts2
fonts.ts2 inventories.ts2 varbits.ts2 models.ts2
enums.ts2 params.ts2 npcs.ts2 …
import { Fonts } from './game/fonts';
<text text="Confirm" w="fill" h={16} font={Fonts.B12_FULL} color={0xff981f} />

An unnamed entry arrives as ENUM_4136 or PARAM_1448. Alias it with typeof in a file of your own:

src/refs.ts2
/**
* The game's own table of skill icons: `enum_255`, keyed by skill.
* …
*/
export declare const SkillIcons: typeof GameEnums.ENUM_255;

.get() and .at() read the game’s tables exactly as they read yours, and the value’s type comes from the table:

src/game-data.ts2
const icon = SkillIcons.get(Skills.WOODCUTTING);
return (
<layer x={0} y={0} w="fill" h={26}>
{icon !== null && <sprite x={0} y="center" w={25} h={25} sprite={icon} />}

Items, NPCs and world objects carry parameters. When your project names them, each one reads as a field of the item, typed by the parameter:

src/game-data.ts2
const needs: Int = axe.item.levelrequire!;

The ! marks a field your project’s parameter types declare. A parameter read is never null: an item that does not set it reads the default. param() reads a parameter with no field, by its dictionary reference:

src/game-data.ts2
const runeNeeds: Int = param(rune, AxeParams.LEVEL_REQUIRED);

paramText, paramSprite and inventory.param(slot, …) are its siblings; see param. A row the game ships is read with .int(), .text() and .sprite(); see Collections.