Skip to content

Types

Every value in ts2 has a type, and types that look alike cannot be swapped.

The basic three:

Type
IntA whole number. There is no floating point anywhere.
StringText.
BooleanTrue or false.

Everything else names a kind of game thing. Each is a whole number underneath, and none can be substituted for another:

Type
SpriteAn image
FontA font
ItemAn item
ItemNameAn item named for display rather than held. Reads the same parameters an Item does
ItemIdAn item’s number where the game keeps it apart from the item itself
ModelA 3D model
AnimationAn animation
TextureA texture
SoundA sound
NpcA kind of NPC
NpcInstanceOne NPC standing in the world, rather than its kind
WorldObjectA kind of world object
WorldObjectShapeThe shape a world object takes
EntityOverlayAn overlay drawn over something in the world
SkillA skill
InventoryA container, named
EnumOne of the game’s tables, as a value
StructOne of the game’s rows, as a value. A Row is one of these
DbTable, DbRow, DbColumnA table, a row and a column of the game’s database
VarPlayerA game variable, named
ComponentIdA component, by address
InterfaceAn interface
RootInterfaceThe frame an interface opens into
CoordA world position. Has .x, .z and .plane
AreaA named area of the world
MapIconAn icon on the world map
WorldMapIdOne of the world maps
ContentCategoryA category the game sorts content by
CharA single character. A key press carries one
IntArray, StringArray, AnyIntArrayAn array the game hands over. See collections
LongA 64-bit whole number. Carried, never computed with

Values of these types come from the generated dictionaries.

.at(i) on a list and .get(k) on a map answer Value | null, unless the index or key is a literal, a const bound to one, or a member of an enum.

src/wares.ts2
const categoryLabel = (names: Map<Category, String>, ware: Ware): String =>
names.get(ware.category) ?? 'Sundries';
src/wares.ts2
const Special = ({ i }: { i: Int }): Component => {
const ware = WARES.at(i);
if (ware === null) {
return null;
}
return (
<text x={0} y={0} w="fill" h={16} text={`Special: ${ware.name}, ${ware.price} gp`}
font={Fonts.PLAIN} valign="middle" shadow color={Colors.YELLOW} />
);
};

Two ways to discharge it, and the value’s kind picks one. A number or a string takes ??, which names what a miss should read as. A record takes === null. === null is the one comparison against something that is not a number. See Declaring your own data.

WARES.at(1 + 1) is Ware | null. Write WARES.at(2), or discharge the read.

TypeCarries
VarpRef<Id, Type>A game variable, and what it holds — a plain number unless the dictionary says 'item' or another kind
VarbitRef<Id, Base, Type>A field inside a game variable, which variable contains it, and what it holds
SkillRef<Id>A skill
InvRef<Id, Size>A container, and how many slots it has
ListRef<Id, Length, Value>One of the game’s tables whose keys are positions, how many entries it has, and their type
MapRef<Id, Rows, Key, Value, FirstKey>One of the game’s tables read by key: its row count, the types of its keys and values, and the key its rows start at
ParamRef<Id, Type>A named parameter of an item, an NPC, a world object or a row, and the type of value it holds
StructRef<Id>One of the game’s rows, named outright. Reads exactly as a Row does

You do not write these. The dictionaries declare them, and you alias what you use:

src/refs.ts2
export declare const SlotIcons: typeof GameEnums.ENUM_904;
src/refs.ts2
export declare const SkillIcons: typeof GameEnums.ENUM_255;

A ListRef reads as a List and a MapRef as a Map; the collections page has both in full.

The type of markup. A function returning Component is a component. It may return null to draw nothing:

src/inventory.ts2
const Slot = ({ item, count, slot }: { item: Item; count: Int; slot: Int }): Component => (

The return type of a function that declares an interface:

src/wares.ts2
export const waresUi = (): UI => (
<ui ref={waresRef} w={252} h={376}>
<Wares />
</ui>
);

The same as a Component, except a UI may not be null.

Markup passed to a component between its tags. See Composing components.

src/chrome.ts2
export const SunkenBox = ({ children }: { children?: Children }): Component => (

What a handler receives as its parameter — which component fired, which option was chosen, where the mouse is. It cannot be stored, passed or returned. Every field is on Event.

A Handler is what an event prop takes: an arrow function, written in place or named, taking the event or nothing. A Callback is a Handler or null, and null is how a handler is taken off a component that already has one — through a ref, or on one arm of a conditional. See Events.

src/chrome.ts2
content.onScrollWheel = (event) => {

A named handler is not a value: it cannot be passed to a function, returned from one, or stored in state.

One right-click option, when a plain string is not enough. label is the menu text, hotkey a key code (Escape is 13), modifiers a mask — 1 for ctrl, 2 for alt, 4 for shift. An empty label with a hotkey is a keyboard-only option. See Hover and menus.

A handle to one component. Its writable properties are the props of the element it is bound to; props writes several at once, options writes the menu; width, height, x, y, scrollX and scrollY can be read back. Declared with useRef inside a render or createRef at module scope. See Refs.

A component by address — what event.component is, and what a component declares to take a component from anywhere, not only one its caller built. A Ref passes where a ComponentId is wanted. Reading geometry through one takes a ! (target.width!).

A world position, read back axis by axis: .x is east–west, .z is north–south, .plane is the floor. Made with tileOf:

src/reference-language-globals.ts2
const LUMBRIDGE = tileOf(3222, 3218, 0);
src/reference-language-globals.ts2
text={`Lumbridge: ${LUMBRIDGE.x}, ${LUMBRIDGE.z}, ${LUMBRIDGE.plane === 0 ? 'ground floor' : `floor ${LUMBRIDGE.plane}`}`}

One row of a table the game ships. Its fields are parameters, read by naming them:

row.int(param): Int — a whole-number parameter, typed by the parameter's own kind
row.text(param): String — a text parameter
row.sprite(param): Sprite — a sprite parameter

A Row is also a Struct, so it can be passed to a component that declares one. A StructRef<Id> is a row named outright in a dictionary, and reads the same way. A record type you declared yourself is not a Row — its fields are ordinary properties, ware.price.

What useInventory returns: the items in a container, by slot. Its accessors are on the collections page.

A collection you declared with listOf or mapOf, or one of the game’s tables through a ListRef or MapRef. Both are ordinary values: a List<Int> can be a prop, a parameter, a return type or another map’s value type. Everything they do is on the collections page.

A state declaration, and the function that writes it. sessionState and persistedState return a StateRef; useState returns the value and a Setter. See useState.

What useInterval and useDelay return. One method, stop(). See Timers.

The types behind x, y, w and h, as the language declares them:

type XPosition =
| Int
| 'left' | 'center' | 'right'
| `${number}%`
| { left: Offset } | { center: Offset } | { right: Offset };
type YPosition =
| Int
| 'top' | 'center' | 'bottom'
| `${number}%`
| { top: Offset } | { center: Offset } | { bottom: Offset };
type Offset = Int | `${number}%`;
type Extent =
| Int
| 'fill' | 'aspect'
| `${number}%`
| { fill: Offset };

A position anchor’s offset may be a pixel count or a proportion of the parent: { right: 4 } is four pixels in from the right edge, { center: '25%' } is centred and then moved a quarter of the parent’s width. { left: '25%' } means the same as the bare "25%".

src/layout.ts2
<sprite x={{ left: '15%' }} y="center" w={36} h={32} item={Items.RUNE_SCIMITAR} itemCount={-1} />
<sprite x="center" y="center" w={36} h={32} item={Items.AMULET_OF_POWER} itemCount={-1} />
<sprite x={{ right: '15%' }} y="center" w={36} h={32} item={Items.LOBSTER} itemCount={5} />

A size anchor takes a pixel count only. { fill: 8 } is the parent less eight; w="10%" is a tenth of it; { fill: '10%' } is refused (TS2E210):

refused/types.ts2
{/* refused: */}
<rect x={10} y={42} w={{ fill: '10%' }} h={16} color={Colors.AMBER} fill />

An anchored value takes exactly one anchor: { left: 3, right: 3 } is refused too. A keyword or a percentage kept in a constants object needs as const, or the prop sees a string (TS2322): const Layout = { HALF: '50%' as const };. Layout and geometry shows every form on screen.

type Align = 'left' | 'center' | 'right';
type VAlign = 'top' | 'middle' | 'bottom' | 'even';
type ItemCountMode = 'auto' | 'never' | 'always';

valign="even" shares the leftover height of a multi-line text into equal gaps — one above the first line, one between each pair, one below the last — and is the same as middle on a single line. There is no horizontal even.

itemCountMode says whether an item slot draws its stack size: auto only for an item that stacks, never and always regardless. It is independent of itemCount, which is the quantity itself.

type ContentType =
| 'fpsCounter' | 'worldView' | 'minimap' | 'compass'
| 'worldMap' | 'worldMapOverview' | 'loginRunes'
| Int;

contentType marks a component as one of the surfaces the client draws itself — the 3D world, the minimap, the world map. A number is accepted for a surface the client does not name.

Declare the type of a component’s props:

src/lists.ts2
const PrayerCell = ({ prayer, unlocked, x, y }: {
prayer: Prayer; unlocked: Boolean; x: Int; y: Int;
}): Component => (

Declare a record type for the rows of a collection:

src/lists.ts2
type Prayer = {
name: String;
level: Int;
icon: Sprite;
};

An enum declares a set of named whole numbers, and is always a constant.

There are no classes, no generics of your own, and no unions beyond string literals and | null.

A prop declared as a plain number cannot be passed to something expecting a sprite. Declare the prop with the type it actually holds — icon: Sprite, not icon: Int.

You read a collection with an index or key that is not a literal, and used the result without saying what a miss should give. Add ?? fallback for a number or a string; test === null and return null for a record. See A read that can miss.

Arithmetic is whole-number, and division truncates. 7 / 2 is 3, not 3.5. To round up, add the divisor less one first: (n + 7) / 8.