← Docs

Plugin Development

Build server plugins with AssemblyScript and the BadLads WebAssembly API.

What is as-badlads?

as-badlads is the BadLads WebAssembly host API implementation in AssemblyScript, used for building server plugins. The as prefix stands for AssemblyScript. This is not JavaScript — it compiles to WebAssembly and runs natively in the game engine.

GitHub npm Template

Prerequisites

Node.js is required for the npm package manager.

Creating a Plugin from Scratch

  1. Find your BadLads plugins folder (BadLadsPlugins):

    • Windows client: C:\Users\{you}\AppData\Local\BadLads\Saved\
    • Server: {ServerDirectory}\BadLads\Saved\
  2. Create a new directory inside BadLadsPlugins for your plugin and navigate into it.
  3. Initialize the project and install dependencies:

    npm init
    npm install --save-dev assemblyscript
    npm install @chemicalheads/as-badlads
    npx asinit .
  4. Create a plugin.json file (must be all lowercase) in your plugin root. This describes your plugin to the game.

  5. Add exportRuntime: true to both the release and debug targets in asconfig.json:

    {
      "targets": {
        "debug": {
          "outFile": "build/debug.wasm",
          "textFile": "build/debug.wat",
          "sourceMap": true,
          "debug": true,
          "exportRuntime": true
        },
        "release": {
          "outFile": "build/release.wasm",
          "textFile": "build/release.wat",
          "sourceMap": true,
          "optimizeLevel": 3,
          "shrinkLevel": 0,
          "converge": false,
          "noAssert": false,
          "exportRuntime": true
        }
      }
    }
  6. Open assembly/index.ts and start writing your plugin. Compile with:

    npm run asbuild
  7. Open BadLads in Sandbox mode. Hot reload is enabled by default. To reload plugins at runtime:

    /reloadplugins

Event Hooks

Export these functions from your assembly/index.ts to listen for game events:

import { BadLadsObject } from "@chemicalheads/as-badlads";

// Called when the plugin starts
export function onPluginStart(pluginId: i32): void {}

// Called when the plugin stops
export function onPluginStop(pluginId: i32): void {}

// Called every server tick
export function onPluginTick(deltaTime: f64): void {}

// Called when a player joins
export function onPlayerLogin(playerState: BadLadsObject): void {}

// Called when a player leaves
export function onPlayerLogout(playerState: BadLadsObject): void {}

// Called on chat message — return false to block it
export function onPlayerChatMessage(
  playerState: BadLadsObject,
  messageBuffer: ArrayBuffer,
  channel_index: i32
): bool {
  return true;
}

// Called when something dies
export function onLivingDeath(
  victim: BadLadsObject,
  killerPlayerState: BadLadsObject
): void {}

// Called when a player changes job
export function onBecomeJob(
  playerState: BadLadsObject,
  jobName: ArrayBuffer
): void {}

Core Types

BadLadsObject

A 64-bit object ID (u64) used for all host API lookups. The first 32 bits are object search flags, the next 32 bits are the actual object ID.

Vector

3D vector with x, y, z as f32. Up is +Z.

Rotation

Roll, yaw, pitch as f32.

Transform

Combines position (Vector), rotation (Rotation), and scale (Vector).

Color

RGB color with values 0–255 per channel.

Object Flags

Bitmask enum for filtering objects:

BadLadsObjectFlags.None
BadLadsObjectFlags.PlayerStates
BadLadsObjectFlags.PlayerCharacters
BadLadsObjectFlags.Vehicles
BadLadsObjectFlags.EstateVolumes
BadLadsObjectFlags.EstateObjects
BadLadsObjectFlags.Buildables
BadLadsObjectFlags.All

API Reference

Function Description
getPlayerCharacter(playerState)Get the PlayerCharacter from a PlayerState.
isObjectValid(object)Check if an object ID is valid.
postGlobalChatMessage(msg, color?, isEventful?)Send a message to all players.
postPlayerMessage(playerState, msg, color?, isEventful?)Send a message to a specific player.
decodeString(buffer)Decode a UTF-8 ArrayBuffer to a string.
getBadLadsVersion()Get the host game version string.
getObjectsIdsByType(flags)Get all object IDs matching the given flags.
setObjectTransform(object, transform)Set an object's position, rotation, and scale.
getObjectTransform(objectId)Get an object's current transform.
spawnObject(flag, index, transform, async?)Spawn a new object in the world.
getPlayerAccountId(playerState)Get a player's account ID (e.g. SteamId).
getPlayerName(playerState)Get a player's display name.
kickPlayer(playerState)Kick a player from the server.
banPlayer(playerState)Ban a player from the server.
setLivingHealth(playerState, newHealth)Set a living entity's health (0 = death).
setDoorState(doorObject, state)Set door state: 0 closed, 1 open forward, -1 open backward.
getEstateVolumeBuildables(estateVolume)Get all buildables within an estate volume.
giveItem(playerState, itemId, stackSize?, autoStack?)Give an item to a player.
getPlayerJob(playerState)Get a player's current job.
setPlayerJob(playerState, job, ...)Set a player's job.
getObjectBounds(object)Get an object's bounding box (center + extents).
rayTrace(start, end, channel?, ignored?)Line trace through the collision world.
getGameTime()Get the server game time in seconds.
getEconomyCurrencyValue()Get the current economy currency value.

Tips

  • To explore the full API, middle-click on an import in your IDE to jump to the type definitions in badlads.ts.
  • Functions prefixed with __host are low-level host bindings. Use the wrapper functions instead unless you know what you're doing.
  • String parameters from events arrive as ArrayBuffer. Use decodeString(buffer) to convert them.
  • The game has hot reload enabled by default in Sandbox mode. Use /reloadplugins to pick up new plugins without restarting.

Example: Chat Filter Plugin

A minimal plugin that filters a word from chat messages:

import {
  BadLadsObject,
  decodeString,
  postPlayerMessage,
  Color
} from "@chemicalheads/as-badlads";

export function onPluginStart(pluginId: i32): void {
  // Plugin loaded
}

export function onPlayerChatMessage(
  playerState: BadLadsObject,
  messageBuffer: ArrayBuffer,
  channel_index: i32
): bool {
  const message = decodeString(messageBuffer);

  if (message.toLowerCase().includes("bad")) {
    postPlayerMessage(
      playerState,
      "Watch your language!",
      new Color(255, 100, 100),
      false
    );
    return false; // Block the message
  }

  return true; // Allow the message
}