Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

JSON RPC Plugins

JSON RPC Plugins let you use any language you desire, as long as you can run it from a single executable file. They follow the JSON-RPC 2.0 Specification

The server communicates with the plugin by sending messages to stdin and expects responses in stdout. All stderr is printed to the console.

Register custom /commands by returning {registeredCommands: ['foo', 'bar']} (registers command /foo and /bar) in the init method.

Omegga Methods (You can access these)

MethodArgumentsDescriptionReturns
logline (string)Prints message to omegga console
errorline (string)Same as log but with different colors
infoline (string)Same as log but with different colors
debugline (string)Same as log but with different colors
warnline (string)Same as log but with different colors
traceline (string)Same as log but with different colors
store.getkey (string)Get an object from plugin storeObject
store.set[key (string), value (any)]Store an object in plugin store
store.deletekey (string)Remove an object from plugin store
store.wipenoneRemove all objects from plugin store
store.countnoneCount number of objects in plugin storeInteger
store.keysnoneGet keys for all objects in plugin storeList of Strings
execcmd (string)Writes a console command to Brickadia
writelncmd (string)Same as exec
broadcastline (string)Broadcasts a message to the server
whisper{target: string, line: string}Sends a message to a specific client
middlePrint{target: string, line: string}Sends a middle print message to a specific client
getPlayersnoneOnline playersList of Players
getAllPlayerPositionsnoneAn array of objects with fields pos and player.List of { Player Object(…), Position(…), isDead(bool) }
getRoleSetupnoneServer rolesJSON Data
getBanListnoneList of bansJSON Data
getSavesnoneSaves in the saves directoryList Strings
getSavePathname (string)The path to a specific saveString
getSaveDatanoneCurrent save as brs-js dataBRS Object
clearBricks{target: string, quiet: bool}Clears a specific player’s bricks
clearAllBricksquietClears all bricks on the server
saveBricksname (string)Save bricks to a save named name
loadBricks{name: string, offX, offY, offY, quiet: bool}Load bricks of save named name
loadBricksOnPlayer{name: string, player: string, offX, offY, offY}Load bricks of save named name on player clipboard
readSaveDataname (string)Parses save into a brs-js save object, returns the objectBRS Object
loadSaveData{data: object, offX, offY, offY, quiet: bool}Builds brs file from data, loads the file
loadSaveDataOnPlayer{data: object, player: string, offX, offY, offY}Builds brs file from data, loads the file onto a player’s clipboard
changeMapmap (string)Change map to specified map name, returns successBoolean
player.gettarget (string)Gets the player by their name or UUID.{name, id, controller, state, host: bool}
player.getRolestarget (string)Target’s rolesList of Strings
player.getPermissionstarget (string)Target’s permissionsRecord<string, boolean>
player.getNameColortarget (string)Target’s name colorRGB Hex String
player.getPositiontarget (string)Target’s position[number, number, number] or null
player.getPawntarget (string)Target’s pawn namestring or null
player.getGhostBricktarget (string)Target’s ghost brick{targetGrid, location, orientation}
player.getPainttarget (string)Target’s current paint selection{materialIndex, materialAlpha, material, color}
player.isCrouchedtarget (string)Check if target is crouchedboolean
player.isDeadtarget (string)Check if target is deadboolean
player.getTemplateBoundstarget (string)Target’s template/selection bounds{minBound, maxBound, center}
player.getTemplateBoundsDatatarget (string)Target’s template/selection as brs-js save dataBRS Object
player.clearBricks{target, quiet}Clears target’s bricks
player.loadBricks{target, saveName}Loads save file to target’s clipboard
player.loadSaveData{target, data, offX, offY, offZ}Loads brs-js save data to target’s clipboard
player.loadDataAtGhostBrick{target, data, rotate=true, offX, offY, offZ, quiet}Loads brs-js save data at target’s selection bounds
player.killtarget (string)Kills the target player
player.damage{target, amount}Damages target by amount
player.heal{target, amount}Heals target by amount
player.giveItem{target, item}Gives target an item
player.takeItem{target, item}Removes item from target
player.setTeam{target, teamIndex}Sets target’s team
player.setMinigame{target, index}Adds target to minigame at index
player.setScore{target, minigameIndex, score}Sets target’s score in minigame
player.getScoretarget (string)Gets target’s score in minigamenumber
player.setLeaderboard{target, key, value}Sets leaderboard value for target
player.getLeaderboardtarget (string)Gets leaderboard value for targetnumber or null
plugin.gettarget (string)Gets info on the target pluginObject
plugin.emit[target (string), event (string), …args (any)]Emit a custom event to the target plugin

Plugin Methods (You implement these)

MethodArgumentsDescriptionRequired
initconfig objectReturns a start result, called on plugin start
stopnoneReturns something, called on plugin stop
bootstrap[{ omegga info (host, version, etc) }]Run when plugin is started for base data
plugin:players:raw[[… [player username, displayName, id, controller, state]]]Lists players on the server
plugin:emit[event, from, …args]Fired when another plugin sends an event
line[brickadiaLog string]A brickadia console log
start[{map}]On brickadia server start
host[{name, id}]When the host is detected
version[-1 or the CL number]When the version is detected
unauthorizednoneOn brickadia server fails an auth check
join[{name, id, state, controller}]Run when a player joins
leave[{name, id, state, controller}]Run when a player leaves
cmd:command[playerName, …args]Runs when a player runs a /command args
chatcmd:command[playerName, …args]Runs when a player runs a !command args
chat[playerName, message]Runs when a player sends a chat message
interact{brick_asset: string;player: { id: string; name: string; controller: string; pawn: string };position: [number, number, number];}Runs when a player clicks a brick with an interact component. data is parsed JSON if line (from interact component) starts with “json:{“your”: “json”}`. Uses interact log field
event:NAME[>player from click<, …args]Runs when an interact component has `event:NAME: arg1,arg2,arg,3,
mapchange[{map}]Runs when the map changes
autorestart[autorestart config]Runs server has an autorestart scheduled
minigamejoin{player: {name, id}; minigameName: string}Deprecated as of EA3. Runs when a player joins a minigame. Note that minigameName is not unique between minigames. minigameName will be null if player leaves all minigames. This will run before join
wirelog[raw string]Runs when a [Wire Graph] log line is emitted. raw is the text after the [Wire Graph] prefix
wirecmd:command[…args]Runs when a [Wire Graph] log starts with command args. command is lowercased; args are the remaining space-separated words

Folder Structure

In a plugins directory create the following folder structure:

  • plugins/myPlugin - plugin folder (required)
  • plugins/myPlugin/doc.json
  • plugins/myPlugin/omegga_plugin - executable plugin file (required)

omegga_plugin (example, node javascript)

#!/usr/bin/env node

const readline = require('readline');
const { EventEmitter } = require('events');
const {
  JSONRPCServer,
  JSONRPCServerAndClient,
  JSONRPCClient,
} = require('json-rpc-2.0');

// events
const ev = new EventEmitter();

// stdio handling
const rl = readline.createInterface({
  input: process.stdin,
  output: process.stdout,
  terminal: false,
});

// rpc "server and client" for responding/receiving messages
const rpc = new JSONRPCServerAndClient(
  new JSONRPCServer(),
  // the client outputs JSON to console
  new JSONRPCClient(async blob => console.log(JSON.stringify(blob)))
);

// on stdin, pass into rpc
rl.on('line', line => {
  try {
    rpc.receiveAndSend(JSON.parse(line));
  } catch (e) {
    console.error(e);
  }
});

// regexes for matching brickadia console logs
const GENERIC_LINE_REGEX =
  /^(\[(?<date>\d{4}\.\d\d.\d\d-\d\d.\d\d.\d\d:\d{3})\]\[\s*(?<counter>\d+)\])?(?<generator>\w+): (?<data>.+)$/;
const LOG_LINE_REGEX =
  /\[(?<date>\d{4}\.\d\d.\d\d-\d\d.\d\d.\d\d:\d{3})\]\[\s*(?<counter>\d+)\](?<rest>.*)$/;

ev.on('line', line => {
  const logMatch = line.match(LOG_LINE_REGEX);
  if (!logMatch) return;
  const {
    groups: { rest },
  } = logMatch;
  const dataMatch = rest.match(GENERIC_LINE_REGEX);
  if (dataMatch) ev.emit('logData', dataMatch.groups);
  else ev.emit('logLine', rest);
});

// list of players
let players;

// get a player by name
const getPlayer = name => players.find(p => p.name === name);

// watch console logs for a pattern, then remove the listener
function watch(exec, pattern) {
  return new Promise(resolve => {
    function listener(line) {
      const match = line.match(pattern);
      // listener removes itself on a match
      if (match) {
        ev.off('logLine', listener);
        resolve(match.groups);
      }
    }
    // add the listener
    ev.on('logLine', listener);

    // run the console command
    rpc.notify('writeln', exec);
  });
}

// get a player's position
async function getPlayerPos(name) {
  const player = getPlayer(name);
  if (!player) return;

  // get player position from player controller
  const pawnRegExp = new RegExp(
    `BP_PlayerController_C .+?PersistentLevel\\.${player.controller}\.Pawn = BP_FigureV2_C'.+?:PersistentLevel.(?<pawn>BP_FigureV2_C_\\d+)'`
  );
  const { pawn } = await watch(
    `GetAll BP_PlayerController_C Pawn Name=${player.controller}`,
    pawnRegExp
  );

  // get player position from pawn
  const posRegExp = new RegExp(
    `CapsuleComponent .+?PersistentLevel\\.${pawn}\\.CollisionCylinder\\.RelativeLocation = \\(X=(?<x>[\\d\\.-]+),Y=(?<y>[\\d\\.-]+),Z=(?<z>[\\d\\.-]+)\\)`
  );
  const { x, y, z } = await watch(
    `GetAll SceneComponent RelativeLocation Name=CollisionCylinder Outer=${pawn}`,
    posRegExp
  );

  return [x, y, z].map(Number);
}

// emit a console log
const log = (...args) => rpc.notify('log', args.join(' '));

// when available players updates - plugin:players:raw is emitted
rpc.addMethod('plugin:players:raw', ([playerArr]) => {
  // update the players list
  players = playerArr.map(p => ({
    name: p[0],
    id: p[1],
    controller: p[2],
    state: p[3],
  }));
});

// ping command
rpc.addMethod('chatcmd:ping', ([name, ...args]) => {
  rpc.notify('broadcast', `pong @ ${name} + ${args.length} args`);
});

// player position command
rpc.addMethod('chatcmd:pos', async ([name]) => {
  log('player', name, 'requests position');
  const [x, y, z] = await getPlayerPos(name);
  rpc.notify('broadcast', `<b>${name}</> is at ${x} ${y} ${z}`);
});

// pass lines into the event emitter
rpc.addMethod('line', ([line]) => {
  ev.emit('line', line);
});

// receive config object in init
rpc.addMethod('init', async ([config]) => ({ registeredCommands: [] }));
rpc.addMethod('stop', async () => 'ok');

See also