← Docs
Version 0.9.5

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 at the server's configured plugin tick rate
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,
  channelIndex: 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 {}

onPluginTick fires at the rate set by PluginTickRate. Any single call into your plugin is bounded twice over: by PluginFuelPerCall, the work it may do, and by PluginCallTimeoutMilliseconds, the time it may take. All three are described under Plugins in the server configuration reference.

Core Types

BadLadsObject

A 64-bit object ID (u64) used for all host API lookups. The low 8 bits hold the object search flag and bits 32–63 hold the object serial, but treat the whole value as opaque.

Serials are handed out in order and never reused, so an ID is stable for as long as the object it names lives, and stays dead afterwards rather than later resolving to something else. IDs for the same object compare equal, so they are safe to store and to use as map keys. Confirm one still resolves with isObjectValid before acting on it.

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, returning the distance to what it hit.
rayTraceObject(start, end, channel?, ignored?)The same trace, returning the object that was hit. 0 for a miss, or for scenery with no object ID.
getCharacterPlayerState(character)Get the PlayerState owning a PlayerCharacter. The reverse of getPlayerCharacter; 0 for an AI.
getGameTime()Get the server game time in seconds.
getEconomyCurrencyValue()Get the current economy currency value.
getLivingHealth(object)Get a living entity's current health, or -1 if it has none.
getLivingMaxHealth(object)Get a living entity's maximum health. setLivingHealth clamps to this.
getPlayerMoney(playerState)Get a player's balance, or -1 if it could not be read.
addPlayerMoney(playerState, amount)Pay (positive) or charge (negative) a player. A charge the player cannot afford is refused whole.
getPlayerItems(playerState, containers)Get the stacks a player is carrying in the given containers.
getPlayerItemCount(playerState, itemId, containers)Count how many of one item a player holds.
removeItem(playerState, itemId, amount)Take items from a player. Returns the amount that could not be removed.
getItemDefinitionCount()Number of item definition slots. Some are empty.
getItemDefinitionName(itemId)Get an item definition's stable class name.
getItemDefinitionIdByName(name)Resolve a definition name to its ID, or -1. Use this instead of hardcoding IDs.

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,
  channelIndex: 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
}