On this page 9 sections
This page is the plugin reference for three bridges between your C code and the game:
- console commands: a command the player (or a config file, or a key binding) types in the game console runs your C function;
- the game's own scripting: the game's quests and dialogue are written in Python 2.1; your plugin can run Python code and read Python values, so everything the game's scripts can do, your plugin can do too, on any game build;
- Python functions in C: a C function your plugin adds can be called from Python mods, game scripts and dialogue conditions.
A minimal example: a console command mymod_humanity that prints the player's Humanity and lowers it by one.
#include <stdio.h>
#include "blse_api.h"
static const BLSE_HostAPI *host;
static void BLSE_CALL humanity_cmd(int argc, const char *const *argv, const char *args, void *user) {
char value[32], line[64];
if (!host->python_eval(host->context, "FindPlayer().humanity", value, sizeof value, NULL)) {
host->console_print(host->context, "no game is running\n");
return;
}
snprintf(line, sizeof line, "Humanity was %s\n", value);
host->console_print(host->context, line);
host->python_exec(host->context, "FindPlayer().HumanityAdd(-1)");
}
BLSE_EXPORT int BLSE_CALL BLSE_PluginLoad(const BLSE_HostAPI *h) {
host = h;
if (h->api_version != BLSE_API_VERSION) return 0;
if (!h->add_command(h->context, "mymod_humanity", "Shows and lowers the player's Humanity", humanity_cmd, NULL))
h->log("the command name is taken");
return 1;
}Important, before you start:
- Main thread only. Every function on this page does nothing (or returns 0) from another thread.
- The game's scripts exist only while a game runs.
FindPlayer(),Gand the level's entities exist after the main menu has loaded a level; before that, Python calls about them fail. - Inside game code, Python cannot run at once. From an event handler delivered inside game code (such as
blse.entity_output)python_execis queued andpython_evalfails. Seepython_exec. - The game's Python is version 2.1: no
True/Falsekeywords, nowith, few modules.
Words used on this page#
| Word | Meaning |
|---|---|
| Console | The game's command window (the ~ key). Commands typed there, in config files (autoexec.cfg) or bound to keys run console commands |
__main__ |
The Python module the game's own scripts run in. FindPlayer(), Find(name), FindClass(cls) and G live there |
G |
The game's global script variables, saved with every save game. Quests keep their state there |
| Traceback | Python's error report: what went wrong and where. BLSE prints it to the game console |
| Safe point | The end of the server frame, where BLSE runs code that had to wait (queued python_exec code, Python event handlers) |
blse_native |
The Python module BLSE fills with the functions plugins add. Python mods and game scripts call blse_native.<name>(...) |
| Engine restart | The game reloads its engine (for example after some video settings); plugins stay loaded |
Console commands#
static void BLSE_CALL on_heal(int argc, const char* const* argv, const char* args, void* user) {
/* argv[0] is the command name; args is the text after it as typed */
host->console_print(host->context, "healing\n");
}
/* in BLSE_PluginLoad: */
host->add_command(host->context, "mymod_heal", "Heals the player.", on_heal, NULL);- Names are 1-63 characters from
[A-Za-z0-9_]; prefix them with your mod name.blse_is reserved. add_commandreturns 0 if the name is invalid, already used by the game or another mod. Adding the same name again from the same plugin replaces the handler.- Commands can be added during
BLSE_PluginLoador later; they become usable when the engine console is ready (shortly after plugins load) and stay registered for the rest of the game, including across engine restarts. - Handlers run on the main thread, from the console or from config files/binds. A plugin that returns 0 from
BLSE_PluginLoadkeeps no working commands.
add_command#
typedef void (BLSE_CALL *BLSE_CommandFn)(int argc, const char *const *argv, const char *args, void *user);
int add_command(void *context, const char *name, const char *help, BLSE_CommandFn callback, void *user);Adds a console command that calls callback. (0.4)
When to use it: a debug command while you develop, a command players bind to a key (bind F6 mymod_toggle), a setting players change in autoexec.cfg.
name: 1-63 characters ofA-Z a-z 0-9 _. Start it with your mod's name; names starting withblse_are reserved for BLSE.help: the command's help text, as the game's own commands have;NULLfor none.callback: your handler.argcis the number of words,argv[0]the command name andargv[1]... the words after it,argseverything after the name as typed (so you can parse quotes yourself). All strings are borrowed: valid only during the call.user: any pointer, passed back tocallbackunchanged.- Returns 1, or 0 if the name is invalid or taken by the game or another mod,
callbackisNULL, or it is not called on the main thread. The reason is written toblse.log(console: command 'x' not added: ...). - If your plugin is disabled after a crash, its commands stay registered but answer "not available".
static void BLSE_CALL speed_cmd(int argc, const char *const *argv, const char *args, void *user) {
if (argc < 2) { host->console_print(host->context, "usage: mymod_speed <factor>\n"); return; }
speed_factor = (float)atof(argv[1]);
}console_print#
void console_print(void *context, const char *text);Prints text to the game console exactly as given: end lines with "\n" yourself. (0.4) Returns nothing; off the main thread, or with text NULL, nothing is printed. For your plugin's log file use log instead.
Using the game's scripting API#
The game's own scripting interface is Python: quests and dialogue use functions such as FindPlayer(), Find(name), FindClass(cls) and the G variables. python_exec and python_eval give native plugins the same access, with no addresses involved, so they work on any game build:
char humanity[32]; size_t n = 0;
if (host->python_eval(host->context, "FindPlayer().humanity", humanity, sizeof(humanity), &n)) { /* "7" */ }
host->python_exec(host->context, "FindPlayer().HumanityAdd(-1)");- Code runs in
__main__, like the game's own scripts. Errors print a traceback to the game console and return 0. - Called from inside game code (an
entity_outputhandler),python_execqueues the code and returns 2; it runs at the end of the server frame.python_evalcannot wait and returns 0 there. Gand level entities exist only while a game is running (after the main menu loads a level).
python_exec#
int python_exec(void *context, const char *code);Runs Python 2.1 statements in __main__, like the game's own scripts. (b0.5.2)
When to use it: anything the game's scripts can do: give money (FindPlayer().MoneyAdd(100)), set a quest state (FindPlayer().SetQuest("MyQuest", 2)), set a G variable, call a Python mod's function.
code: one or more statements; separate lines with"\n".- Returns 1 if the code ran, 2 if it was queued because the call came from inside game code (it then runs at the next safe point, the end of the server frame), 0 on an error (a traceback is printed to the game console), when Python is unavailable,
codeisNULL, or it is not called on the main thread.
host->python_exec(host->context, "G.MyMod_Visited = 1\nFindPlayer().MoneyAdd(50)");python_eval#
int python_eval(void *context, const char *expression, char *buffer, size_t capacity, size_t *size);Evaluates one Python expression in __main__ and copies str(result) into buffer. (b0.5.2)
When to use it: read a value the game keeps in Python: a stat (FindPlayer().humanity), money (FindPlayer().money), a G variable, whether an entity exists.
expression: an expression, not a statement ("G.MyMod_Visited", not"x = 1").- The text is copied following the output buffer rules: cut to
capacity - 1bytes and NUL-terminated;*sizereceives the full length. - Returns 1, or 0 on an error (traceback in the console), when Python is unavailable, when called from inside game code (it cannot wait), or off the main thread.
- Numbers come back as text: convert them with
atoi/atof.Nonecomes back as"None".
char money[32];
if (host->python_eval(host->context, "FindPlayer().money", money, sizeof money, NULL) && atoi(money) >= 100) { /* can pay */ }Adding Python functions#
add_python_function(context, name, help, fn, user) (b0.5.7) adds blse_native.<name> for game scripts, Python mods and dialogue conditions. Native code can add functions to the game's Python this way:
static int BLSE_CALL bonus(const BLSE_Value* args, int argc, BLSE_Value* result, void* user) {
if (argc != 1 || args[0].type != BLSE_VALUE_INT) {
result->type = BLSE_VALUE_STRING; result->s = "expects an int"; result->size = 14;
return 0; /* raises RuntimeError("mymod_bonus: expects an int") */
}
result->type = BLSE_VALUE_INT;
result->i = args[0].i * 2;
return 1;
}
host->add_python_function(host->context, "mymod_bonus", "mymod_bonus(n) -> int", bonus, NULL);- Values are
BLSE_VALUE_NONE,INT(i),FLOAT(f) andSTRING(sandsize, NUL-terminated, may contain NUL bytes).*resultstarts as None. A STRING result is copied when the function returns, so a static or stack buffer is fine. - Names are Python identifiers of 1-63 characters. Prefix them with your mod name. Adding a name again replaces your earlier function; a name another mod uses is refused (0).
- The function runs on the main thread inside the calling Python code, so
python_execandpython_evalwork from it. It may be called from dialogue, so keep it quick. - Functions survive engine restarts. A plugin that crashes in one is disabled and its functions removed (see below).
The crash rules are described in When a plugin crashes. After a crash, a call to one of the plugin's functions raises RuntimeError ("not available") in Python, also through a reference a script kept earlier. The Python side is described in Functions from native plugins.
add_python_function#
typedef struct BLSE_Value {
uint32_t type; /* BLSE_VALUE_NONE, BLSE_VALUE_INT, BLSE_VALUE_FLOAT, BLSE_VALUE_STRING */
int32_t i; /* INT */
double f; /* FLOAT */
const char *s; /* STRING: size bytes, NUL-terminated, may contain NUL bytes */
size_t size;
} BLSE_Value;
typedef int (BLSE_CALL *BLSE_PythonFunctionFn)(const BLSE_Value *args, int argc, BLSE_Value *result, void *user);
int add_python_function(void *context, const char *name, const char *help, BLSE_PythonFunctionFn fn, void *user);Makes fn callable from Python as blse_native.<name>(...). (b0.5.7)
When to use it: a dialogue condition that needs C code (blse_native.mymod_can_bribe(npc.GetName())), a calculation a Python mod asks your plugin for, sharing your plugin's state with Python mods.
name: a Python identifier: 1-63 characters ofA-Z a-z 0-9 _, not starting with a digit or with__. Start it with your mod's name.help: a short description of the function ("mymod_bonus(n) -> int");NULLfor none.fn: your function. It gets the call's arguments as an array ofargcvalues and aresultthat starts asNone. Arguments may beNone, ints (longs that fit), floats and strs (NUL bytes allowed), at most 32; anything else raisesTypeErrorin Python before your function is called.- Your function returns 1 with
*resultfilled (or left asNone), or 0 to raiseRuntimeErrorin Python; aSTRINGresult is then the error message (prefixed with the function's name). ASTRINGresult is copied when your function returns. - Strings in
argsare borrowed: valid only during the call. blse_nativeis set in__main__for game scripts and dialogue conditions; Python mods use it too (blse_native.mymod_bonus(2)).- Returns 1, or 0 if the name is invalid or taken by another mod,
fnisNULL, Python is unavailable, or it is not called on the main thread. The reason is written toblse.log(python: function 'x' not added: ...).
Use from Python:
import blse_native
print blse_native.mymod_bonus(21) # 42In a dialogue condition (a .dlg column, see Conversations): blse_native.mymod_bonus(1) > 1.