Appearance
Serverside NPCs serverside
The NPC this and its level view for serverside NPC scripts (npc.server.d.ts), on top of the server globals.
Source
Generated from templates/server/data/scripts/npc.server.d.ts. To change this page, edit the JSDoc in that file.
Overview
Ambient declarations for SERVERSIDE NPC scripts (the serverScript baked into a level's NPCs). NPC scripts also see everything in globals.d.ts (echo, triggerClient, server flags, timers, ...).
Inside every handler, this is the NPC itself (use function declarations, not arrow functions). Property writes are server-authoritative: they apply for EVERYONE in the level. Unknown properties are a per-NPC state bag that persists between handler calls (Graal's state-on-this convention).
export function onCreated(): void
The NPC was instantiated: the first player activated the level, or the level's file was updated (hot reload re-creates every NPC).export function onPlayerEnters(player: Player): void
A player entered the NPC's level (fires again after a hot reload).export function onPlayerChats(player: Player, chat: string): void
A player in the level typed chat (script-set chat does not fire this).export function onUpdate(dt: number): void
Every server tick (20 TPS); dt is the tick length in seconds.export function onAction<Name>(...): void
A triggerAction landed on this NPC's shape. From a clientside script's triggerAction(x, y, name, ...params) the signature is (player: Player, ...params); from a serverside this.level.triggerAction(x, y, name, ...params) it is just (...params). <Name> is the action name with its first letter uppercased: triggerAction(x, y, 'openDoor') calls onActionOpenDoor.export function onShotAt(x: number, y: number, data: any): void
A projectile stopped in this NPC's level — hit a wall or ran out of lifetime — at (x, y). Coordinates are the projectile's space: level tiles, or gmap-global tiles when the level is a gmap member.datais the shoot call's payload. Fires on every server-scripted NPC of the level the projectile stopped over.export function onShot(data: any): void
A projectile hit this NPC's shape (the same hitbox onAction uses). Also fires on the NPC's CLIENTSIDE script on every client.export function onMovementFinished(): void
A this.move() with option 8 ended — reached its target, or stopped early at a wall (option 4). Fires once per flagged move.
setTimeout/setInterval/sleep work per-NPC: timer callbacks keep this bound to the NPC, and timers die when the NPC unloads (level hot reload).
Declarations
NpcLevel interface
ts
interface NpcLevelA serverside NPC's view of its level (this.level): lets the NPC inspect the level, fire triggerAction at shapes in it, and put more NPCs into it.
NpcLevel.name
ts
readonly name: stringThe level's file name, e.g. "start.glvl".
NpcLevel.triggerAction
ts
triggerAction(x: number, y: number, action: string, ...params: any[]): voidFires onAction<Name> on every NPC whose shape contains (x, y) — tile units. Serverside handlers run here; clientside handlers run on every client in the level.
NpcLevel.onwall
ts
onwall(x: number, y: number, w?: number, h?: number): booleanTrue when a blocking collision tile intersects [x, x+w) × [y, y+h), in tile units (fractions allowed). Omit w/h to test the single tile containing (x, y). Tiles outside the level count as blocked. Other blocking NPCs (see dontblock) and players' lower bodies block too; this NPC itself never does. A player's walking footprint is the lower body: onwall(player.x, player.y + 1, 2, 1).
NpcLevel.tiletype
ts
tiletype(x: number, y: number): numberCollision type of the tile containing (x, y): 1 walkable, 22 blocking, etc. -1 when out of range.
NpcLevel.shoot
ts
shoot(x: number, y: number, gan: string, angle: number, speed: number,
lifetime: number, data?: any): Projectile | nullSpawns a projectile with its CENTER at (x, y) — this NPC's level-local tiles; when the level is a gmap member the projectile is promoted to gmap space and crosses member seams. gan is the animation it renders (e.g. 'arrow'; the flight angle picks the gani direction). angle is radians, Graal-style: 0=right, PI/2=up, PI=left, 3*PI/2=down. speed is tiles/sec (max 100), lifetime seconds (max 60). data is any JSON-serializable payload, delivered to every callback.
The projectile stops on the first blocking tile (onShotAt for scripts in that level), on lifetime expiry (onShotAt), on an NPC shape (onShot on that NPC, server + client) or on a player (onShot on the hit player's clientside weapon scripts). Returns null on bad arguments.
NOTE: inside NPC scripts always call this.level.shoot — a bare level is the player-scoped global for weapon scripts and returns null here.
NpcLevel.putnpc
ts
putnpc(x: number, y: number, serverScript: string, clientScript?: string): NpcThis | nullSpawns a LOCAL NPC at (x, y) — this NPC's level-local tiles. The script arguments are inline TypeScript SOURCE strings (Graal putnpc2-style), compiled through the same pipeline as level NPCs: the first compile of a novel script briefly blocks the server tick; identical sources are cached. Scripts can this.join('<class>') to pull in shared code.
The NPC is never saved to the level file: it lives until destroy() or server restart, surviving level hot reloads (/clearnpcs <level> in RC clears strays). Returns the NPC's full this-style handle — set x/ image/chat etc. on it directly — or null when the level can't be resolved.
NpcThis interface
ts
interface NpcThisthis inside a serverside NPC handler: the NPC itself. Writes to its properties are server-authoritative and replicate to everyone in the level; unknown properties are a per-NPC state bag.
NpcThis.id
ts
readonly id: numberRuntime id of this NPC (unique per server run).
NpcThis.name
ts
readonly name: string"npc-<index>" (level NPCs) or "localnpc-<id>" (putnpc NPCs).
NpcThis.x
ts
x: numberPosition in tiles (fractional allowed). Assigning teleports the NPC for everyone and cancels any move(). While a move() runs these read the interpolated position.
NpcThis.y
ts
y: numberPosition in tiles (fractional allowed). Assigning teleports the NPC for everyone and cancels any move(). While a move() runs these read the interpolated position.
NpcThis.dir
ts
dir: numberFacing: 0 up, 1 left, 2 down, 3 right (Graal convention). Values wrap.
NpcThis.move
ts
move(dx: number, dy: number, time: number, options?: number): voidGraal's move(dx, dy, time, options): glides the NPC by (dx, dy) tiles over time seconds. Every client animates it smoothly on its own; the position isn't streamed while it moves.
Options is a bitmask (Graal's move flags; combine with |):
ts
1 cache — queue behind the moves already pending instead of
replacing them (without it, pending moves are dropped
and this one starts from the current position)
2 append — dx/dy are measured from the end of the queued path
instead of the current position
4 blockcheck — stop at the last clear spot when the NPC would walk
into a wall (a character NPC's walking footprint, else
its shape/image); drops the rest of the queue
8 onMovementFinished fires when this move ends (reached or blocked)
16 apply direction — face the move's dominant axis when it startstime <= 0 moves instantly (on the next update). Assigning x or y cancels every pending move.
NpcThis.image
ts
image: stringAsset-relative image, e.g. "images/sign.png". '' = invisible (unless showCharacter).
NpcThis.chat
ts
chat: stringChat bubble text ('' clears it).
NpcThis.head
ts
head: stringAppearance when rendered as a character ('' = the gani defaults).
NpcThis.body
ts
body: stringAppearance when rendered as a character ('' = the gani defaults).
NpcThis.colors
ts
colors: string[]Body colors as 'r,g,b' strings, like player.colors: [0] skin, [1] coat, [2] sleeves, [3] shoes, [4] belt. Always five entries; defaults to the player defaults. Assigning an index (this.colors[1] = '255,0,0') or an array updates everyone in the level; invalid indices or values are ignored. Only visible after showCharacter().
NpcThis.ani
ts
ani: stringCurrent animation name; only visible after showCharacter().
NpcThis.showCharacter
ts
showCharacter(): voidRenders the NPC as a gani character (idle) for everyone in the level.
NpcThis.setAni
ts
setAni(name: string): voidPlays an animation on this NPC for everyone in the level.
NpcThis.dontblock
ts
dontblock(): voidGraal dontblock(): players and other NPCs walk through this NPC, for everyone in the level. NPCs block by default — a character with its lower body (like walls block players), anything else with its shape or image; an NPC with neither never blocks.
NpcThis.blockagain
ts
blockagain(): voidGraal blockagain(): undoes dontblock() (blocking is the default).
NpcThis.blocking
ts
readonly blocking: booleanFalse after dontblock().
NpcThis.setShape
ts
setShape(x: number, y: number, w: number, h: number): voidSets the triggerAction hitbox: offset and size in PIXELS relative to the NPC's top-left. Without it, the image's pixel size is the shape; no image and no setShape = no shape, so no onAction can land.
NpcThis.level
ts
readonly level: NpcLevelThe level this NPC lives in.
NpcThis.join
ts
join(name: string): booleanJoins this script to a class (scripts/classes/<name>.ts): the class's exported handlers fire after this script's own, its exported helpers become callable on this, and its onCreated runs now with this script as this. A serverside join also joins the class's clientside half (<name>.client.ts, when one exists) on every client. Re-joining re-checks the class version and re-fires its onCreated. Returns false when the class doesn't exist.
NpcThis.leave
ts
leave(name: string): booleanRemoves a joined class's handlers from this script.
NpcThis.joinedclasses
ts
readonly joinedclasses: readonly string[]Currently joined class names, in join order.
NpcThis.destroy
ts
destroy(): booleanRemoves this NPC everywhere — serverside script (timers included) and every client's copy — if it is a LOCAL NPC (level.putnpc). Level-file NPCs can't be destroyed: returns false. Idempotent.
NpcThis.[key]
ts
[key: string]: anyAnything else is per-NPC script state, kept between handler calls.