TypeScript

KCDS runs CommonJS JavaScript, so you compile TypeScript to .js before the server starts your resource. Each release includes declarations for the whole SDK, which give your editor every type, member and side.

Getting the declarations

KCDS-Types-<version>.zip is on the download page, next to the server. Use the version matching your server.

FileDeclares
shared.d.tswhat both sides have: console, require and the shared named types
server-api.d.tsserver scripts: the kcds object with the server's types and events
client-api.d.tsclient scripts: the kcds object with the client's types and events

Server and client scripts are checked against different files, so using a client type in a server file is a compile error.

Each SDK type is a value and a type at the same path, such as kcds.world.Door. A name on its own, such as Door, is an error.

Setting up a project

resources/
  hello-ts/
    package.json
    tsconfig.server.json
    tsconfig.client.json
    types/            the three .d.ts files from the zip
    src/
      server/main.ts
      client/main.ts
    dist/             compiler output, which the manifest points at

package.json:

{
  "name": "hello-ts",
  "version": "1.0.0",
  "scripts": {
    "build": "tsc -p tsconfig.server.json && tsc -p tsconfig.client.json"
  },
  "devDependencies": {
    "typescript": "^5.9.3"
  },
  "kcds": {
    "serverScripts": ["dist/server/main.js"],
    "clientScripts": ["dist/client/main.js"]
  }
}

tsconfig.server.json:

{
  "compilerOptions": {
    "target": "ES2022",
    "module": "commonjs",
    "lib": ["ES2022"],
    "types": [],
    "strict": true,
    "rootDir": "src/server",
    "outDir": "dist/server"
  },
  "files": ["types/shared.d.ts", "types/server-api.d.ts"],
  "include": ["src/server/**/*.ts"]
}

tsconfig.client.json is the same, with client in place of server.

Why these settings?

  • "lib": ["ES2022"] and "types": [] leave out browser and Node declarations, so a script sees only what the SDK declares. If your server code uses Node modules, add @types/node and "types": ["node"] to the server project.
  • Two separate projects keep each side's names out of the other.
  • Only dist/client/ goes to players, because the manifest sends the folder of the client entry script (see Project structure).

Writing the code

src/server/main.ts:

const { Events, Chat } = kcds.core;
const { Player } = kcds.players;

function welcomeLine(name: string): string {
  return `Welcome, ${name}!`;
}

Events.on('playerReady', (player: kcds.players.Player) => {
  Chat.sendToPlayer(player, welcomeLine(player.nickname), { author: 'Server' });
});

Events.fromClient('hello:count', (player: kcds.players.Player) => {
  player.emit('hello:count', { players: Player.all().length });
});

src/client/main.ts:

const { Events } = kcds.core;
const { Hud, Key } = kcds.ui;

interface CountAnswer {
  players: number;
}

Key.bind('f7', () => {
  Events.toServer('hello:count');
});

Events.on('hello:count', (data: CountAnswer) => {
  Hud.showNotification(`${data.players} players online`);
});

Building

npm install
npm run build

Then enter ensure hello-ts in the server console. Build and ensure again after each change. Running tsc --watch on both projects rebuilds as you save.

What gets checked

The compiler catches wrong names, members the SDK does not have, and types from the wrong side. Your editor also shows each member's summary and return type.

Most parameters are typed any. The SDK checks values when the script runs, and throws a TypeError or RangeError naming the argument.

The declarations compile with strict and skipLibCheck: false. Handler parameters are typed, so none is an implicit any:

  • game and SDK events, through KcdsTypes.ServerEvents and KcdsTypes.ClientEvents
  • your own event names, with KcdsTypes.EventHandler, whose arguments are any
  • Messages.handle, with KcdsTypes.MessageHandler, which receives the payload and reply
  • PropPlacer.on, with KcdsTypes.PlacementHandler, which receives a pose or null, and whether it is valid
  • Key.bind, Web.on and ActionHint.create

Event payloads contain whatever the sender put in, so describe your own with an interface, like CountAnswer above.

Named types

Your code can also use these types from the KcdsTypes namespace:

TypeDescribes
InventorySnapshot, InventoryRowInventory.get and player.getInventory(), and each row
InventoryUnit, InventoryResult{ id, amount } for Inventory.remove and transfer, and the answer to any change
InventoryChangethe change of playerInventoryChanged
PickpocketedItema row of playerPickpocketed's items
LevelEditDefinitionthe argument of LevelEdit.apply
DialoguePage, DialogueOptionwhat Dialogue.open and update take
CookPotSnapshot, CookPotFooda pot from CookPot.all or get; food is 'goulash', 'lentil', 'soup' or null
ScreenEffectInfoan entry of Hud.effectList()
Appearancea body's look
ProgressionSnapshota player's skills and perks
JoinRequesta join request

Deprecated members still work but are left out of the declarations. For example, a client blip's state has been markState since 0.3.4, as on the server.