API reference
The types and functions in plugin_api.h (API version 1.6), and the interfaces plugins share.
The API is one C header, core/include/readyup/plugin_api.h. This page describes version 1.6. The header has the exact signatures and a comment on every member. When this page and the header differ, the header is right.
Versions
#define READYUP_PLUGIN_API_VERSION_MAJOR 1
#define READYUP_PLUGIN_API_VERSION_MINOR 2
#define READYUP_PLUGIN_API_VERSION ((MAJOR << 16) | MINOR)
#define RU_API_VERSION_MAJOR(v) /* v >> 16 */
#define RU_API_VERSION_MINOR(v) /* v & 0xFFFF */
#define RU_API_HAS(ptr, member) /* is member inside ptr->struct_size? */| Version | Added |
|---|---|
| 1.0 | Logging, server commands, chat, chat and console commands, ticks, lifecycle events, posted tasks, the data folder. |
| 1.1 | Center HTML, players, raw engine events, log lines, schema and entities, round control, admins, config, plugin-to-plugin interfaces, state across reloads. |
| 1.2 | Untagged log lines, command flags, ru subcommands, on_frame, feature state, the current map. |
| 1.3 | entity_set_abs_origin: move an entity. |
| 1.4 | workshop_download_progress: Steam Workshop download progress. |
| 1.5 | entity_remove: remove an entity. |
| 1.6 | Center panel priorities: center_html_to_slot_prio, center_html_all_prio, center_html_release. |
A plugin that requires 1.6 in ru_plugin_info.api_version can call everything. A plugin that requires an older minor must check RU_API_HAS(api, member) before it calls a newer member. The rules are on Threading and ABI.
Exports
A plugin exports exactly these three functions, with READYUP_PLUGIN_EXPORT.
readyup_plugin_info
const ru_plugin_info* readyup_plugin_info(void);Called right after the library is opened, before anything else. It must not call into the core. Return a pointer to a static struct:
typedef struct ru_plugin_info {
uint32_t struct_size; /* sizeof(ru_plugin_info) */
uint32_t api_version; /* READYUP_PLUGIN_API_VERSION you need */
const char* name; /* file name without ".so", [a-z0-9_-] */
const char* version;
const char* author;
const char* description;
} ru_plugin_info;The core refuses the plugin if name does not match the file name, if the API major differs, or if the plugin needs a newer minor than the core has.
readyup_plugin_load
int readyup_plugin_load(const ru_api* api, uint32_t core_api_version);Runs on the game thread. Register your commands, ticks and subscriptions here. Keep the api pointer: it stays valid until unload returns.
Return 0 on success. Any other value aborts the load. The core then removes what you already registered and closes the library.
readyup_plugin_unload
void readyup_plugin_unload(void);Runs on the game thread, when none of your code is running. Stop and join your threads, and free your memory. The core removes all your registrations afterwards, whether or not you removed them yourself.
ru_api
The core passes this table to readyup_plugin_load. Every function takes api->self as the first argument. The core uses it to know which plugin owns a registration.
| Member | Type |
|---|---|
struct_size | uint32_t. RU_API_HAS uses it. |
api_version | uint32_t. The core's API version. |
core_version | const char*. The core's build, for example "0.9.0 (abc1234)". |
self | ru_plugin*. Your plugin's handle. Pass it to every call. |
Call these functions only from the game thread, unless the table says "any thread". From a callback you always are on the game thread.
1.0: basics
| Function | What it does |
|---|---|
log(self, level, msg) | Any thread. One line to the console as plugin[<name>]: <msg>. RU_LOG_DEBUG lines only show with debug=1. ru_logf(api, level, fmt, ...) is a printf-style helper. |
server_command(self, cmd) | Queue a console command, for example "mp_restartgame 1". 1 = queued. |
chat_all(self, msg, flags) | Chat to everyone, with the Ready Up prefix. RU_CHAT_RAW leaves the prefix out. |
chat_to_slot(self, slot, msg) | Chat to one player slot. No prefix. |
register_chat_command(self, name, fn, user) | A chat command such as ".hello". Must start with . or !, then [a-z0-9_-]. Must not be taken by the core or another plugin. Only real players trigger it. 0 on failure; the reason is logged. |
register_console_command(self, name, fn, user) | A console and RCON command, one token such as "hello_status". ru and core commands are reserved. |
on_tick(self, fn, user) | fn once per simulating server frame. |
subscribe(self, type, fn, user) | Lifecycle events of one type, or all with RU_EVENT_ANY. See Events. |
unregister(self, handle) | Remove any registration. Safe from inside that same callback. |
post_to_game_thread(self, fn, user) | Any thread. Run fn(user) on the game thread at the next frame. Dropped if the plugin unloads first. |
slot_for_steamid(self, steamid64) | Slot of a connected player, or -1. |
data_dir(self) | Your data folder: csgo/readyup/plugins/<name>/, as an absolute path. |
Both command callbacks get an ru_command_ctx:
typedef struct ru_command_ctx {
uint32_t struct_size;
uint64_t steamid64; /* 0 for the console */
int slot; /* player slot, or -1 if unknown */
int is_console; /* 1 for the server console / RCON */
const char* name; /* sender name, "Console" for the console */
const char* text; /* the full trimmed line, e.g. ".hello world" */
int argc; /* argv[0] is the command itself */
const char* const* argv;
} ru_command_ctx;Tick callbacks get an ru_tick_info: frame (simulating frames since start), now (monotonic seconds) and, since 1.2, simulating.
1.1: output and players
| Function | What it does |
|---|---|
center_html_to_slot(self, slot, html, seconds) | A center-screen HTML panel to one player. Never a broadcast. |
center_html_all(self, html, seconds) | The same panel to every connected human, one at a time. Returns how many got it. |
get_player(self, slot, out) | The connected player in a slot. |
get_player_by_steamid(self, steamid64, out) | The connected human with this Steam64 ID. |
for_each_player(self, fn, user) | Every connected human, then every known bot. Return 0 from fn to stop. |
You own the ru_player struct. Set struct_size before you pass it in. It has slot, steamid64 (0 for bots), team, is_bot, connected, name[128], and since 1.2 userid (the <N> in log lines, which kickid takes).
1.1: engine events and log lines
| Function | What it does |
|---|---|
subscribe_game_event(self, name, fn, user) | An engine game event by name, such as "player_death". Delivered at once, on the game thread, while the engine dispatches it. Keep the callback short and never keep ev. |
ev_get_int, ev_get_float, ev_get_uint64, ev_get_string | Read a key from the event, with a default. |
ev_get_player_slot, ev_get_player_controller, ev_get_player_pawn | The player behind a key such as "userid" or "attacker". Pointers are valid for this frame only. |
subscribe_log_line(self, fn, user) | Every server log line, queued. The core's own output is filtered out. |
1.1: schema and entities
| Function | What it does |
|---|---|
schema_offset(self, class, field) | Byte offset of a networked field, looked up by name, for example ("CCSPlayerPawn", "m_EconGloves"). -1 if unknown. Never hard-code offsets. |
entity_system_status(self) | RU_ENTSYS_PENDING, _OK or _FAILED. Until OK, every lookup below returns NULL. |
entity_by_index, entity_from_handle, entity_handle_of | Find entities. Keep handles across frames, never pointers. |
entity_classname(self, entity) | For example "weapon_ak47". |
entity_mark_changed(self, entity) | Send the whole entity again, for fields written after its first snapshot. |
econ_attr_set_by_name, entity_change_subclass, entity_set_model, entity_set_bodygroup_by_name | Item attributes, knife subclass, model and bodygroup. These need engine-surface.skins.json. Without it they return 0. |
1.1: round control, admins, config
| Function | What it does |
|---|---|
set_round_termination_suppressed(self, on) | Keep rounds from ending, for warmup and practice. There is no function to force a round end. |
set_chat_name_prefix(self, steamid64, prefix) | A chat prefix for one player, such as [CAP]. NULL or "" clears it. Removed on unload. |
is_admin(self, steamid64) | Any thread, may block. Asks the admin provider first, then the core. |
set_admin_provider(self, fn, user) | Help decide who is an admin. One provider per plugin, and several plugins can have one: a player is an admin when any provider says so. fn returns 1, 0, or -1 for "no opinion". It can run on any thread. The match and essentials plugins register one. |
config_get(self, key, buf, len) | A setting from cfg/ReadyUp/<plugin>.cfg, then from the [<plugin>] section of readyup.cfg. Returns the length, or -1 if the key is not set. |
debug_enabled(self) | Any thread. debug=1. |
config_dir(self) | Any thread. The folder that holds readyup.cfg. |
1.1: between plugins, and across reloads
| Function | What it does |
|---|---|
provide_interface(self, name, version, iface) | Publish a struct of function pointers under a name such as "readyup.match.v1". One provider per name. Removed on unload. |
get_interface(self, name, min_version) | Look one up. NULL if missing or too old. Valid only until your callback returns: look it up again in every callback, because the provider can reload. |
stash_put(self, key, data, len) | Keep up to 1 MiB of bytes in core memory until the next load of the same plugin. len 0 deletes it. Not written to disk. |
stash_get(self, key, buf, cap) | Read it back. Returns the full size, or -1. |
1.2
| Function | What it does |
|---|---|
log_untagged(self, level, msg) | Any thread. Like log, but the line reads [ReadyUp] <msg>, without the plugin tag. For log formats tools already parse. |
register_chat_command_ex(self, name, flags, fn, user) | With RU_CMD_HIDE, the sender's line is not shown in chat. Best effort: it needs the ClientCommand hook. |
register_console_command_ex(self, name, flags, fn, user) | With RU_CMD_OBSERVE, the engine still runs the command and you only watch it, for example tv_delay. Observers never conflict. |
register_ru_subcommand(self, name, fn, user) | ru <name> in the console and .ru <name> in chat both call fn. argv[0] is ru or .ru. The core's own subcommands are reserved. Chat senders are not checked: call is_admin yourself. |
on_frame(self, fn, user) | Every server frame, simulating or not. For timers that must run during a map change or on an empty server. |
feature_state(self, name) | 1 on, 0 pending, -1 off, for a core feature (knife, ready_hud, ...), an engine dependency (fn:<name>, cmdbuf, eventmgr, ...), or events_live. The same names ru selftest shows. |
current_map(self) | The current map, or "" before the first map. |
1.3 to 1.6
| Function | Since | What it does |
|---|---|---|
entity_set_abs_origin(self, entity, origin) | 1.3 | Move an entity to origin[0..2]. On a player pawn this is a teleport. 0 if unavailable or the origin is off the map. Game thread. |
workshop_download_progress(self, id, downloaded, total) | 1.4 | Bytes downloaded of a Workshop item. 0 when nothing is downloading. Any thread. |
entity_remove(self, entity) | 1.5 | Remove an entity at the end of the frame. Refuses the world and player controllers. Game thread. |
center_html_to_slot_prio(self, slot, html, seconds, priority) | 1.6 | Send center HTML at a priority. 1 sent, -1 refused because a higher panel of another plugin is up (try again later), 0 failed. |
center_html_all_prio(self, html, seconds, priority) | 1.6 | The same to every player. Returns how many got it. |
center_html_release(self, slot) | 1.6 | Give up your panel on a slot (-1: all), so lower panels can draw at once. |
Each player has one center panel. The core keeps who owns it until that panel's seconds run out. The priorities are RU_HTML_PRIO_INFO (10), RU_HTML_PRIO_HUD (50, what center_html_to_slot and center_html_all use), RU_HTML_PRIO_NOTICE (70, one-off cards), RU_HTML_PRIO_MENU (80, a menu the player opened) and RU_HTML_PRIO_ALERT (90, must be seen now).
Handles
typedef uint64_t ru_handle; /* 0 = failure */Each registration returns a handle. It is unique for the life of the server process.
Events
The core turns engine game events and server log lines into one set of lifecycle events. Once its engine event listener is up, events come from the engine. Before that, they come from log lines. source says which. The core picks one source for each fact, so you do not get the same round or player change twice.
| Type | Value | Fields set |
|---|---|---|
RU_EVENT_ANY | 0 | Only for subscribe: all types. |
RU_EVENT_MAP_START | 1 | map |
RU_EVENT_MATCH_START | 2 | CS2's Match_Start (end of warmup, mp_restartgame). |
RU_EVENT_ROUND_START | 3 | round (1-based if known, else 0) |
RU_EVENT_ROUND_END | 4 | winner, reason, team_ct_score, team_t_score |
RU_EVENT_PLAYER_CONNECT | 5 | slot, steamid64, name |
RU_EVENT_PLAYER_DISCONNECT | 6 | slot, steamid64, name, reason |
RU_EVENT_PLAYER_TEAM | 7 | slot, steamid64, name, team, old_team |
Every event also has map, the current map. Unknown numbers are -1 (slot, scores) or 0 (winner).
Enums
typedef enum ru_log_level {
RU_LOG_INFO = 0, RU_LOG_WARN = 1, RU_LOG_ERROR = 2,
RU_LOG_DEBUG = 3 /* only with debug=1 */
} ru_log_level;
typedef enum ru_team {
RU_TEAM_UNASSIGNED = 0, RU_TEAM_SPECTATOR = 1, RU_TEAM_T = 2, RU_TEAM_CT = 3
} ru_team;
enum { RU_CHAT_RAW = 1u << 0 }; /* chat_all: no prefix */
enum { RU_CMD_HIDE = 1u << 0, RU_CMD_OBSERVE = 1u << 1 }; /* *_command_ex flags */Interfaces
Plugins publish interfaces with provide_interface, and others find them with get_interface. Each interface is a C struct of function pointers that starts with struct_size. Members are only ever added at the end. Check struct_size before you use a member, and look the interface up again in every callback.
readyup.match.v1
core/include/readyup/match_iface.h. Published by match.so. The core reads it to fill the match part of /status.
| Member | What it does |
|---|---|
get_status(out) | Fills ru_match_status: update_safe, summary_json (the flat /status summary), state_json (the match state, or NULL when nothing is loaded) and ru_mode. |
Without match.so, /status shows "match_plugin": "none" and update_safe: true.
readyup.fleet.v1
core/include/readyup/fleet_iface.h. Published by fleet.so. The core reads get_status for /status. The match and skins plugins use the rest.
| Member | Thread | What it does |
|---|---|---|
get_status(out) | any | Link state, server ID, reconnects, spool size, the offline auto-pause countdown. Returns 0 when standalone. |
instance_id() | any | Random per load of fleet.so. When it changes, fleet.so was reloaded and your handlers are gone. Register them again. |
connection_state() | any | An ru_fleet_link_state: standalone, unenrolled, enrolling, connecting, online, offline, rejected. |
send_event(type, json, epoch, flags) | any | Queue a message to the platform. RU_FLEET_RELIABLE spools it to disk until the platform acks it. Never blocks. |
send_reply(type, json, epoch, flags, ref) | any | The same, as an answer to message ref. |
send_snapshot(reason, extra_json) | any | Send the full match state now. Only while online. |
register_handler(type, fn, user) | game | Receive platform messages of one type, or "*" for all. You must unregister in your own unload. |
unregister_handler(id) | game | Remove a handler. |
publish_state(json, availability) | any | The current match state and availability, used in hello and snapshots. |
add_capability(name) | game | Tell the platform what this server can do, for example match.v1 or skins.v1. |
Handlers also get two local messages that never go over the wire: local.connection (the link went up or down) and local.offline_timeout (the offline auto-pause time was reached).
Selftest lines
core/include/readyup/selftest_iface.h. A plugin adds its own lines to ru selftest by publishing readyup.selftest.<its name>:
typedef struct ru_selftest_iface_v1 {
uint32_t struct_size;
void (*run)(ru_selftest_add_fn add, void* ctx);
} ru_selftest_iface_v1;run calls add(ctx, status, name, detail) once per check. status is OK, FAIL, PEND, SKIP, INFO or WARN. run can be called from any thread, even while the plugin unloads. It must be fast, must not block, and must not call any API member except log.