On this page 71 sections
- Words used on this page
- Save-game data
- save_set
- save_get
- save_set_versioned, save_get_versioned
- Settings files
- ini_get, ini_set
- World, inventories, controls and screens
- spawn_entity
- teleport_entity
- give_item
- remove_item
- item_count
- equip_item
- drop_item
- move_item
- NPC AI
- fire_input
- set_relationship, relationship
- set_enemy, npc_enemy
- start_schedule, npc_schedule
- npc_state
- move_npc
- follow_npc, npc_leader
- npc_senses, set_npc_sense
- npc_conditions, npc_weapon
- blse.npc_schedule, blse.npc_state
- Animation
- list_sequences, lookup_sequence
- play_animation, stop_animation
- animation_state
- set_animation_rule
- add_animation_filter
- activity_id, activity_name
- blse.anim_started, blse.anim_ended, blse.anim_chosen
- add_model_animations, model_animations
- add_item_class, item_classes
- The shared database
- db_get, db_set
- Values on entities, finding entities, JSON files
- entity_data_set, entity_data_get, entity_data_keys, entity_data_find
- entity_position
- find_entities
- json_get, json_set
- Models, translated strings, frame rate
- bone_names, bone_position
- model_scale, set_model_scale
- translate, translation_language
- frame_stats
- Appearance
- bone_transform, set_bone_transform, reset_bones, modified_bones
- flex_names, flex_value, set_flex, release_flex
- entity_skin, set_entity_skin
- body_groups, set_body_group
- render_color, set_render_color, set_render_mode
- set_entity_material, model_materials, entity_materials
- Player outfits
- Camera and sounds
- camera_get, camera_set
- camera_view, camera_override
- play_sound, stop_sound, play_ui_sound
- play_sound_at, play_sound_on, stop_sound_on, sound_length
- Mod folders and file filters
- provide_file
- add_file_filter, add_map_filter
- list_mods, mod_file_owner, mod_info
- Companions
- set_companion
- list_companions
- block_companion_map
- blse.companion
This page is the plugin reference for everything that touches the game world and your mod's data:
- data kept in the save game (
save_set, the shared database, values stored on entities); - settings files players edit, and JSON files shared by every save;
- the world: spawning and moving entities, inventories, NPC behaviour, animation, models and their looks, the player's outfits, the camera and sounds;
- mod folders: giving the game files from memory, changing game files and maps as they load;
- companions that follow the player from map to map.
Come here when your plugin should remember something, change something in a running level, or change the game's files. Every function behaves like its Python twin; the Python pages (world, data, NPC AI, animation, appearance, mod folders, companions) have more background and examples. This page gives the C side: the exact parameters and return values.
A minimal example: a plugin that counts the player's kills and keeps the count in the save game.
#include <string.h>
#include "blse_api.h"
static const BLSE_HostAPI *host;
static int kills;
/* A save was loaded: read our value back (0 if this save has none). */
static void BLSE_CALL on_loaded(const char *topic, const void *payload, size_t size, void *user) {
size_t n = 0;
kills = 0;
if (!host->save_get(host->context, "mymod.kills", &kills, sizeof kills, &n) || n != sizeof kills) kills = 0;
}
/* A new game: start from zero. */
static void BLSE_CALL on_new_game(const char *topic, const void *payload, size_t size, void *user) { kills = 0; }
/* Someone died: if the player killed them, count it and store the new count at once.
Field 3 of blse.entity_killed is the attacker's classname; payload_field is the
helper from getting-started.md#payloads. */
static void BLSE_CALL on_killed(const char *topic, const void *payload, size_t size, void *user) {
char killer[64];
if (!payload_field(payload, size, 3, killer, sizeof killer) || strcmp(killer, "player")) return;
++kills;
host->save_set(host->context, "mymod.kills", &kills, sizeof kills);
}
BLSE_EXPORT int BLSE_CALL BLSE_PluginLoad(const BLSE_HostAPI *h) {
host = h;
if (h->api_version != BLSE_API_VERSION || !BLSE_HOST_HAS(h, save_get)) return 0;
h->subscribe(h->context, BLSE_MSG_GAME_LOADED, on_loaded, NULL);
h->subscribe(h->context, BLSE_MSG_NEW_GAME, on_new_game, NULL);
h->subscribe(h->context, BLSE_MSG_ENTITY_KILLED, on_killed, NULL);
return 1;
}Important, before you start:
- Main thread only, unless an entry says "any thread". Call these functions from
BLSE_PluginLoad, a console command, an event handler, a filter or a task added withadd_task. From another thread they return 0 (or -1, orBLSE_NO_ENTITY, as each entry says). See Threads. - Most world functions work only while a level runs (a map is loaded and being played; not in the main menu, not during loading). Outside a level they return 0.
- Nothing in the game exists while your plugin loads. There is no level, no player and no save data yet at
BLSE_PluginLoad:save_setandsave_getreturn 0 there. Register things that must exist early (file filters, item classes, outfits, model animations) at load; do the rest in event handlers. - Entities can disappear at any time (killed, picked up, removed by a script, the level ends). Keep handles, not indexes, between frames, and expect a function to return 0 for an entity that is gone.
- Several features need a recognized game build (
host->build_profileis notNULL): NPC AI, animation, outfits, item classes and others. On other builds they return 0 or -1. - Check newer fields before you call them:
BLSE_HOST_HAS(host, field)tells you whether the BLSE running your plugin has the function (see Checking that a field exists).
Words used on this page#
| Word | Meaning |
|---|---|
| Entity | Anything that exists in a level: the player, NPCs, doors, props, items lying around, triggers. Each has a classname (its kind, such as npc_VPedestrian or item_g_bloodpack) and often a targetname (its name in the map, such as guard_1) |
| Index | An entity's slot number on the server, below 8192. 0 is the world itself, 1 is always the player. A slot is reused when an entity is removed, so an index may later name another entity |
| Handle | A number that names one entity for as long as it exists; after the entity is removed the handle names nothing (no reuse). Functions return handles; every entity parameter takes an index or a handle |
BLSE_NO_ENTITY |
0xFFFFFFFF: "no entity". Returned when there is none, and passed to say "none" (no attacker, clear the enemy, stop following) |
| Level | One loaded map being played. A level runs from blse.level_init to blse.level_shutdown |
| Keyvalue | A setting a map gives an entity, as a key and a text value: targetname = guard_1, model = models/...mdl. Most are read only when the entity spawns |
| Inventory | The items a character (the player, an NPC) or a container (a box, a locker) holds |
| Stack | Several items of one kind kept as one entry with a count (blood packs, ammunition). Weapons and most other items cannot stack: an owner has at most one of each |
| Schedule | A short plan an NPC's AI runs (SCHED_TROIKA_FLEE: run away; SCHED_TROIKA_IDLE_PATROL: walk around). The AI picks a new one when the old one ends or something happens |
| Sequence | One animation stored in a model (walk_all, idle01), numbered in the model's order |
| Activity | What the AI wants to do (ACT_WALK, ACT_IDLE, ACT_RANGE_ATTACK1). The game picks a sequence for the activity from the model |
| Bone | One joint of a model's skeleton (Bip01 Head). Animations move bones |
| Flex | A flex controller: a slider in a face model for one expression or mouth shape (smile, blink, jaw open) |
| Skin, body group | A model's alternative textures (skins), and parts that can be switched (body groups: a hat or none) |
| Material | What a mesh of a model is drawn with: a .vmt file below materials/ naming textures and a shader |
| Units | Distances in the game world. A character is about 72 units tall |
| Angles | Three numbers in degrees: pitch (up/down), yaw (turn left/right), roll (tilt) |
Script globals (G) |
The game's Python variables that it writes into every save. BLSE stores your save data there |
| Recognized build | A game build BLSE knows exactly (host->build_profile is not NULL) |
Text that a function copies out (names, lists, JSON) follows the output buffer rules: pass buffer, its capacity in bytes and a size_t that receives the full length; the text is cut to fit and always ends with a NUL. Call once with buffer NULL and capacity 0 to learn the length. Lists end each line with '\n'; fields within a line are separated by a tab ('\t').
Save-game data#
blse.before_save (b0.5.9) is published right before the game writes a save (also autosaves and level transitions). Data written with save_set in its handler is in that save.
save_set/save_get keep small values (up to 1 MiB each) with the save game. BLSE stores them in the game's own script globals (G.blse_data), which the game writes into every save, replaces on load, keeps across level transitions and clears on a new game. So:
- Write a value whenever it changes, or in a
blse.before_savehandler. - Read your values in
blse.game_loaded(sent on the first frame after a save has been restored), not inblse.level_init. - Keys follow the topic rules, up to 117 bytes (each key is one line in the save, and the game must still read it when BLSE is removed; quotes, backslashes and bytes outside printable ASCII count as 2 to 4); prefix them with your mod name.
blse.keys are reserved. - Both functions return 0 before the game's scripts exist (during plugin load, before the main menu).
- Since b0.5.25,
save_set_versioned(context, key, version, data, size)andsave_get_versioned(context, key, buffer, capacity, size, &version)store a format version with the record: write your current version, and on load convert older data. Values written withsave_setread as version 0. Keys up to 108 bytes. BLSE_MSG_NEW_GAME(blse.new_game, b0.5.25) is published at the first level of a new game, beforeblse.level_initreaches you: clear your in-memory state. Payload: the map name.
Your data's life in short:
| Moment | What happens to the values | What to do |
|---|---|---|
| The game starts, main menu | No save data exists yet; save_get/save_set return 0 until the game's scripts exist |
Nothing |
New game (blse.new_game) |
Empty | Reset your in-memory state |
| Playing | Your values live in memory, in G.blse_data |
save_set whenever a value changes |
A save is written (blse.before_save) |
Whatever is stored now goes into the save | Store anything you keep only in your own variables |
| Level change | Kept, like the rest of the game's globals | Nothing |
A save is loaded (blse.game_loaded) |
Replaced by the save's values | Read your values with save_get |
static void BLSE_CALL on_loaded(const char *topic, const void *map, size_t size, void *user) {
int kills = 0; size_t n = 0;
if (host->save_get(host->context, "mymod.kills", &kills, sizeof(kills), &n) && n == sizeof(kills)) { /* use kills */ }
}
/* whenever it changes: */ host->save_set(host->context, "mymod.kills", &kills, sizeof(kills));The events are described in Level, frame and save events: blse.before_save, blse.game_loaded, blse.new_game.
save_set#
int save_set(void *context, const char *key, const void *data, size_t size);Stores a copy of size bytes from data under key in the save data. (0.3)
When to use it: anything your mod must remember when the player saves and loads: a counter, a quest stage of your own, which reward was given.
key: a name for the value, following the topic rules, at most 117 bytes counted as written in the save (quotes, backslashes and bytes outside printable ASCII count 2 to 4 each). Start it with your mod's name ("mymod.kills"); keys starting withblse.are reserved for BLSE.data,size: the bytes to store, any binary content (a number, a struct, text), at most 1 MiB. BLSE copies them; your buffer can be freed afterwards.dataNULLwithsize0 removes the key.- Returns 1, or 0 if the key is invalid or too long, the value is over 1 MiB, or no game session exists yet (the game's script globals do not exist before the main menu, so it fails at plugin load).
- Store a struct only if its layout never changes, or use
save_set_versionedso you can convert old data.
save_get#
int save_get(void *context, const char *key, void *buffer, size_t capacity, size_t *size);Reads a value stored with save_set (or save_set_versioned). (0.3)
buffer,capacity: where to copy the bytes and how many fit. BLSE copies the firstmin(capacity, length)bytes and adds nothing (no NUL: this is binary data).buffermay beNULLto ask only for the length.size: receives the value's full length in bytes (may beNULL). It is set to 0 when the call fails.- Returns 1 if the key exists, 0 if it does not or the save data is not available yet.
size_t n = 0;
if (host->save_get(host->context, "mymod.names", NULL, 0, &n)) { /* ask for the length */
char *text = (char *)malloc(n + 1);
if (text && host->save_get(host->context, "mymod.names", text, n, &n)) text[n] = 0; /* add the NUL yourself */
free(text);
}save_set_versioned, save_get_versioned#
int save_set_versioned(void *context, const char *key, uint32_t version, const void *data, size_t size);
int save_get_versioned(void *context, const char *key, void *buffer, size_t capacity, size_t *size, uint32_t *version);Like save_set and save_get, with a format version number stored next to the value. (b0.5.25)
When to use them: your saved data is a struct that may change in a later version of your mod. Write your current format's version; when you load, look at the version and convert older data.
version: any number you choose (1, 2, ...).save_get_versionedstores the version found in*version(0 for values written with plainsave_set, so older saves stay readable).versionmay beNULL.- Keys up to 108 bytes (the version is kept under a key of its own).
save_set_versionedwithdataNULLandsize0 removes the record and its version.- Returns like
save_set/save_get.
typedef struct { int kills; int deaths; } StatsV2; /* version 1 had only kills */
StatsV2 s = {0};
uint32_t version = 0; size_t n = 0;
if (host->save_get_versioned(host->context, "mymod.stats", &s, sizeof s, &n, &version)) {
if (version < 2) s.deaths = 0; /* converts version 0/1 data: only the first int was stored */
}
host->save_set_versioned(host->context, "mymod.stats", 2, &s, sizeof s);Settings files#
ini_get(context, mod, section, key, buffer, capacity, &size) copies a value from Bin\loader\blse\config\<mod>.ini (like key_binding; 0 if the file or key is missing); ini_set(context, mod, section, key, value) writes it (NULL removes the key) and keeps the rest of the file. Format and rules as in the Python reference. The example plugin examples/sprint_plugin.cpp reads its settings this way.
A settings file is a plain text file players can edit:
; a comment (also # at the start of a line)
[Sprint]
key = ALT
speed_factor = 1.6ini_get, ini_set#
int ini_get(void *context, const char *mod, const char *section, const char *key, char *buffer, size_t capacity, size_t *size);
int ini_set(void *context, const char *mod, const char *section, const char *key, const char *value);Read and write one setting in your mod's settings file. (b0.5.37)
When to use them: options players change by hand (a key, a speed, a colour) without recompiling your plugin.
mod: the file's name without.ini, 1-63 characters ofA-Z a-z 0-9 _ - .. The file isBin\loader\blse\config\<mod>.ini.section,key: matched without regard to case. Spaces around keys and values are ignored; a value in double quotes keeps its spaces. Nothing after a value is a comment (color = #FF0000is a colour).ini_getcopies the value as text (always text: convert numbers withatoi/atof). Returns 1, or 0 if the file or the key is missing.ini_setwritesvalue, keeping the rest of the file as it was (comments too), and makes the folder and file if needed.valueNULLremoves the key (every copy of it in the section). Returns 1 or 0.- Files are UTF-8, up to 1 MB. Main thread.
- Ship the file with comments explaining each setting, and write missing keys with their defaults so players can find them.
char text[32];
float speed = 1.6f;
if (host->ini_get(host->context, "sprint", "Sprint", "speed_factor", text, sizeof text, NULL)) speed = (float)atof(text);
else host->ini_set(host->context, "sprint", "Sprint", "speed_factor", "1.6"); /* write the default */World, inventories, controls and screens#
spawn_entity(context, classname, origin, yaw, pairs, pair_count) (pairs: key, value, key, value, ...) returns the handle or BLSE_NO_ENTITY; teleport_entity(context, entity, origin, angles) (NULL keeps each); give_item, remove_item(..., count), item_count, equip_item, drop_item, move_item(context, from, to, classname). Controls: menu_add_checkbox, menu_add_slider, menu_add_dropdown(..., items, item_count, selected, ...), menu_add_progress, menu_add_list, menu_list_add, menu_list_clear, menu_value(context, screen, path, buffer, capacity, &size), menu_set_value, menu_set_items; menu_block(context, screen, blocked); hud_in_menus(context, id, on). Messages BLSE_MSG_MENU_CHANGED and BLSE_MSG_MENU_BLOCKED. Main thread. Behaviour as in the Python reference and controls.
The controls, menu_block, hud_in_menus and their events are explained entry by entry on the interface page: Controls, lists and blocked screens. Spawning, teleporting and inventories (b0.5.46) follow here. All of them: main thread, while a level runs. owner, from, to and entity are an index or a handle (1 is the player).
spawn_entity#
uint32_t spawn_entity(void *context, const char *classname, const float origin[3], float yaw,
const char *const *pairs, size_t pair_count);Creates an entity the way a map does, and spawns it into the level. (b0.5.46)
When to use it: an NPC that appears when the player enters a room, an item lying on a table as a reward, a prop.
classname: the kind of entity, 1-63 characters ofA-Z a-z 0-9 _("npc_VPedestrian","item_g_bloodpack","prop_physics").playerandworldspawnare refused.origin: where, in world units; each coordinate between -65536 and 65536.yaw: which way it faces, in degrees.pairs,pair_count: the map's keyvalues as an array of strings, key, value, key, value, ...;pair_countis the number of pairs (not strings), at most 256.NULLand 0 for none. Keys: 1-63 characters ofA-Z a-z 0-9 _; values up to 255 characters.classnameandoriginmay not appear (they are the parameters). Keys the entity does not know are ignored, as in maps.- Returns the new entity's handle, or
BLSE_NO_ENTITYif anything is refused: bad arguments, no level, a class the game does not know, an entity that failed to spawn or removed itself while spawning. Unlike Python'sblse.spawn_entity, the reason is not logged for plugins. - Spawned entities are saved with the game like the map's own.
const char *kv[] = {
"model", "models/character/npc/common/citizen/male/male_citizen_1.mdl",
"targetname", "mymod_guard",
};
float at[3];
if (host->entity_position(host->context, 1, at)) { /* next to the player */
at[0] += 64;
uint32_t guard = host->spawn_entity(host->context, "npc_VPedestrian", at, 180.0f, kv, 2);
if (guard == BLSE_NO_ENTITY) host->log("the guard could not be spawned");
}teleport_entity#
int teleport_entity(void *context, uint32_t entity, const float *origin, const float *angles);Moves an entity somewhere at once, and/or turns it. (b0.5.46)
origin: the new position (three floats, each between -65536 and 65536), orNULLto keep the position.angles: pitch, yaw, roll in degrees, orNULLto keep them. At least one of the two must be given.- Works on the player, NPCs and props; not on the world (index 0).
- Returns 1, or 0 if the entity does not exist or a value is invalid.
const float spot[3] = {120.0f, -340.0f, 16.0f};
host->teleport_entity(host->context, 1, spot, NULL); /* the player, facing as before */give_item#
int give_item(void *context, uint32_t owner, const char *classname);Gives one item to the player, an NPC or a container. (b0.5.46)
When to use it: a reward, a shop of your own (read the price with item_info, take the money through the game's Python pc.MoneyAdd, then give the item).
classname: the item's class, such as"item_g_bloodpack"or"item_w_thirtyeight"(1-63 characters ofA-Z a-z 0-9 _).- The player's item goes through the game's own
GiveItem. If the owner already has one: a stack (blood packs, ammunition) grows by one; a second unstackable item is refused, as the game allows one of each. - Returns 1, or 0 if refused (no such owner, no inventory, an unknown class, already owned and not stackable).
remove_item#
int remove_item(void *context, uint32_t owner, const char *classname, int count);Removes up to count items of a class, the way the game's RemoveItem does (from a stack first, then the item itself). (b0.5.46)
count: how many, at least 1 (values above 100000 count as 100000).- Returns how many were removed: 0 if the owner had none or the arguments are invalid, fewer than
countif it had fewer.
item_count#
int item_count(void *context, uint32_t owner, const char *classname);How many items of a class the owner has, counting stacks (a stack of 5 blood packs is 5). (b0.5.46)
- Returns the number (0 if it has none), or -1 if the owner does not exist or has no inventory, or the arguments are invalid.
if (host->item_count(host->context, 1, "item_g_bloodpack") >= 3) host->remove_item(host->context, 1, "item_g_bloodpack", 3);equip_item#
int equip_item(void *context, uint32_t owner, const char *classname);The character takes an item it owns in its hands (a weapon). (b0.5.46) Returns 1, or 0 if it does not own one or cannot hold it.
drop_item#
int drop_item(void *context, uint32_t owner, const char *classname);Drops one item of the class into the world, as the inventory screen does. (b0.5.46) Returns 1, or 0 if the owner has none or the item may not be dropped (quest items).
move_item#
int move_item(void *context, uint32_t from, uint32_t to, const char *classname);Moves an item (a whole stack) from one inventory to another: the player, NPCs, containers. (b0.5.46)
When to use it: a stash box of your own, an NPC handing the player its weapon, taking a companion's items.
- The same item entity moves, with its ammunition and state.
- Not the item the character holds in its hands: switch away first (
equip_itemwith another item). - Returns 1, or 0 if
fromhas no such item,fromandtoare the same, the item is in hands, ortorefuses it (a full inventory; the item then stays withfrom).
NPC AI#
fire_input(context, entity, input, value, activator); set_relationship(context, npc, target, disposition, priority) with BLSE_DISPOSITION_HATE/FEAR/LIKE/NEUTRAL, relationship(context, npc, target, &disposition, &priority); set_enemy(context, npc, target) (BLSE_NO_ENTITY clears and calms), npc_enemy(context, npc); start_schedule(context, npc, name), npc_schedule(context, npc, buffer, capacity, &size), npc_state(context, npc) (BLSE_NPC_STATE_*, -1 if not an NPC); move_npc(context, npc, goal, position, run) (goal BLSE_NO_ENTITY: to position); follow_npc(context, npc, leader), npc_leader(context, npc); npc_senses(context, npc, values) (sight, field of view in degrees, vision and hearing multipliers, stealth level), set_npc_sense(context, npc, sense, value); (b0.5.48) why an NPC does not fire: npc_conditions(context, npc, ids, capacity, &count) (the AI condition ids set now: 64 no ammunition, 79 can fire its ranged weapon, 95/96 too close/too far, 97 not facing its enemy, 99 a character it does not hate is in the line of fire, 71 lost its enemy) and npc_weapon(context, npc, classname, capacity, &size, &clip) (the weapon in its hands and the rounds in its clip). Messages BLSE_MSG_NPC_SCHEDULE and BLSE_MSG_NPC_STATE. Main thread. Behaviour as in the Python reference.
Everything goes through the game's own code (the functions its maps and its aiscripted_schedule entity use). (b0.5.45 unless noted.) Main thread, while a level runs; the functions return 1 or 0 unless an entry says otherwise. npc is an NPC's index or handle; other entities likewise.
Things to know first:
- Who fights. Hate makes an NPC fight only if its kind can: civilians (pedestrians, bluebloods, prostitutes, patients) have no attack and take cover instead. Thugs, gangsters, guards and cops attack.
- Enemies stay while hated. The game drops an enemy the NPC does not hate. To start a fight:
set_relationship(npc, target, BLSE_DISPOSITION_HATE, 99), thenset_enemy(npc, target). To end one: neutral again, thenset_enemy(npc, BLSE_NO_ENTITY).
/* An NPC defends the player: whoever hurts the player becomes its enemy. */
static void make_bodyguard(uint32_t npc, uint32_t attacker) {
host->set_relationship(host->context, npc, attacker, BLSE_DISPOSITION_HATE, 99);
host->set_enemy(host->context, npc, attacker);
}fire_input#
int fire_input(void *context, uint32_t entity, const char *input, const char *value, uint32_t activator);Sends an input to any entity (also one without a name) as the map's wiring does. (b0.5.45)
When to use it: open a door ("Open"), kill an NPC ("Kill"), start a map's logic from your plugin.
input: the input's name ("Open","Kill","SetAnimation").value: the input's value as text, up to 1023 characters;NULLor""for none.activator: the entity that "caused" it (maps pass it on), orBLSE_NO_ENTITYfor the player.- Input filters and
blse.entity_inputsee it like any input. - Returns 1 if the entity handled the input, 0 otherwise.
- BLSE keeps every different input text for the rest of the game (an entity may keep a pointer to it), at most 20000.
host->fire_input(host->context, door, "Open", NULL, BLSE_NO_ENTITY);set_relationship, relationship#
int set_relationship(void *context, uint32_t npc, uint32_t target, int disposition, int priority);
int relationship(void *context, uint32_t npc, uint32_t target, int *disposition, int *priority);How an NPC feels about one entity. (b0.5.45)
disposition:BLSE_DISPOSITION_HATE(1, attacks or takes cover),BLSE_DISPOSITION_FEAR(2, runs away),BLSE_DISPOSITION_LIKE(3),BLSE_DISPOSITION_NEUTRAL(4).priority: decides between several enemies, higher first (0 is normal).set_relationshipis saved with the game.relationshipreads it into*dispositionand*priority(either may beNULL), also the game's own relationships (by class) when no mod set one.- Return 1 or 0.
set_enemy, npc_enemy#
int set_enemy(void *context, uint32_t npc, uint32_t target);
uint32_t npc_enemy(void *context, uint32_t npc);set_enemy makes target (a character: the player or an NPC) the NPC's enemy and tells it where the target is. BLSE_NO_ENTITY clears the enemy and calms the NPC: back to idle, its current schedule (taking cover, chasing) ended, so it talks again. An NPC keeps an enemy only while it hates it. Returns 1 or 0. (b0.5.45)
npc_enemy returns the handle of the NPC's enemy, or BLSE_NO_ENTITY (none, not an NPC, or called off the main thread).
start_schedule, npc_schedule#
int start_schedule(void *context, uint32_t npc, const char *schedule);
int npc_schedule(void *context, uint32_t npc, char *buffer, size_t capacity, size_t *size);start_schedule runs one of the NPC's schedules once, then its own AI carries on. (b0.5.45)
schedule: the game's name, with or withoutSCHED_("SCHED_TROIKA_FLEE"or"TROIKA_FLEE"). Each kind of NPC knows its own set (SCHED_TROIKA_COWER,SCHED_VCOP_WANDER_PATROL, ...);npc_scheduleand theblse.npc_scheduleevent show which ones an NPC uses.- It starts when the NPC's running schedule ends, as the map's
ChangeScheduleinput does: a walking NPC first finishes its step of the path (up to about 1.5 seconds). - Returns 1, or 0 if its kind has no such schedule, or it is dead, in a scripted scene or a conversation.
SCHED_TROIKA_FORCED_RANGE_ATTACK1makes an NPC face its enemy and fire regardless, until the enemy dies.
npc_schedule copies the name of the schedule the NPC runs now ("SCHED_TROIKA_IDLE_PATROL"; "" if none). Returns 1, or 0 if it is not an NPC.
npc_state#
int npc_state(void *context, uint32_t npc);The NPC's state, one of the BLSE_NPC_STATE_* numbers: NONE 0, IDLE 1, COMBAT 2, ALERT 3, SCRIPT 4, PLAYDEAD 5, PRONE 6, DEAD 7, FLEEING 8, RETREATING 9, COWERING 10, HUNTING 11, DIALOG 12, OBLIVIOUS 13, CRIMINAL_SUSPICION 14. Returns -1 if it is not an NPC. (b0.5.45)
move_npc#
int move_npc(void *context, uint32_t npc, uint32_t goal, const float position[3], int run);Sends the NPC walking (or running) somewhere. (b0.5.45)
goal: an entity to go to (where it is now).BLSE_NO_ENTITY: go topositioninstead.position: a point in the world, used only whengoalisBLSE_NO_ENTITY(then it must not beNULL).run: 1 runs, 0 walks.- Moves go to a marker BLSE places (an
info_targetnamedblse_goal, reused, never removed), because the game reads its goal again while walking and would crash on a goal entity removed meanwhile. - A move ends a
follow_npc. Returns 1 or 0.
follow_npc, npc_leader#
int follow_npc(void *context, uint32_t npc, uint32_t leader);
uint32_t npc_leader(void *context, uint32_t npc);follow_npc makes the NPC follow leader (the player, another NPC, any entity): when it is more than 160 units away it is sent after it, running beyond 420. (b0.5.45)
leaderBLSE_NO_ENTITYstops following. It also stops when the NPC dies, the level ends, or it is moved withmove_npc; fights, scripted scenes and conversations pause it.- Not kept in saves: store it yourself (for example with
entity_data_set) and call it again after a load and atblse.level_init. For an NPC that goes along through level changes, see Companions. - The game's own follower system is not used (it ends the game for NPCs in a squad, and pedestrians ignore it).
- Returns 1 or 0.
npc_leaderreturns the handle of the entity it follows, orBLSE_NO_ENTITY.
npc_senses, set_npc_sense#
int npc_senses(void *context, uint32_t npc, float values[5]);
int set_npc_sense(void *context, uint32_t npc, const char *sense, float value);How well an NPC sees and hears. (b0.5.45)
npc_senses fills values (an array of 5 floats, must not be NULL):
| Index | Meaning |
|---|---|
| 0 | Sight distance, in units |
| 1 | Field of view, in degrees |
| 2 | The stealth system's vision multiplier in effect (how quickly it notices a sneaking player by sight) |
| 3 | The hearing multiplier in effect |
| 4 | The NPC's stealth level, 1-10 |
set_npc_sense changes one: sense is "sight" (units), "fov" (degrees, 1-360), "vision" or "hearing" (multipliers). A negative vision or hearing gives the game's own value back (from the NPC's stealth level; a value the map set is not restored: read it first). Kept in saves. Both return 1 or 0.
host->set_npc_sense(host->context, npc, "sight", 4096.0f);npc_conditions, npc_weapon#
int npc_conditions(void *context, uint32_t npc, int *ids, size_t capacity, size_t *count);
int npc_weapon(void *context, uint32_t npc, char *classname, size_t capacity, size_t *size, int *clip);Why an NPC does or does not shoot. (b0.5.48)
npc_conditions fills ids with the AI condition numbers set on the NPC now (0-255), at most capacity of them, and stores how many there are in *count (this may be more than capacity). The ones that matter for shooting: it fires only while it has 79 (can fire its ranged weapon). Others it can have instead: 64 no ammunition in the clip, 8 or 95 too close (under 100 units, or under the weapon's minimum), 96 too far, 97 not facing its enemy, 99 a character it does not hate is in the line of fire (46 when that lasts over 2.5 seconds), 102 sight blocked, 71 it lost its enemy.
npc_weapon copies the class of the weapon in its hands (output buffer rules) and stores the rounds in its clip in *clip (may be NULL).
Both return 1, or 0 if it is not an NPC (npc_weapon also when it holds no weapon).
int ids[64]; size_t n = 0;
if (host->npc_conditions(host->context, npc, ids, 64, &n))
for (size_t i = 0; i < n && i < 64; ++i) if (ids[i] == 64) host->log("out of ammunition");blse.npc_schedule, blse.npc_state#
Topics BLSE_MSG_NPC_SCHEDULE and BLSE_MSG_NPC_STATE (b0.5.45). Recognized builds only. Delivered to native subscribers at once, to Python at the end of the frame.
blse.npc_schedule: an NPC started a schedule (every change, also the game's own; frequent: subscribe only while you need it). Payload, five fields: the NPC's handle (decimal), classname, name, the old schedule's name (""if none), the new one's ("SCHED_TROIKA_FLEE").blse.npc_state: an NPC's state changed. The same first three fields, then the old and the new state as words:none,idle,combat,alert,script,playdead,prone,dead,fleeing,retreating,cowering,hunting,dialog,oblivious,criminal_suspicion. Checked ten times a second while anyone listens.
static void BLSE_CALL on_state(const char *topic, const void *payload, size_t size, void *user) {
char handle[16], now[32];
if (payload_field(payload, size, 0, handle, sizeof handle) && payload_field(payload, size, 4, now, sizeof now)
&& !strcmp(now, "fleeing")) { /* (uint32_t)strtoul(handle, NULL, 10) is running away */ }
}(payload_field is the helper from Payloads.)
Animation#
(b0.5.49; check BLSE_HOST_HAS(host, activity_name).) list_sequences(context, entity, buffer, capacity, &size) copies index<TAB>name<TAB>activity<TAB>loops<TAB>seconds lines, one per sequence; lookup_sequence(context, entity, name) returns the number or -1 (a decimal number names a sequence by its number); play_animation(context, entity, sequence, rate, flags) with BLSE_ANIM_LOOP and BLSE_ANIM_STILL; stop_animation(context, entity); animation_state(context, entity, &state) fills a BLSE_Animation (set struct_size first); set_animation_rule(context, who, activity, sequences) (NULL sequences remove it; a plugin's rules end when it declines to load or is disabled after a crash); activity_id(context, name), activity_name(context, number, buffer, capacity, &size).
add_animation_filter(context, filter, user): int filter(void *user, uint32_t entity, int activity, int translated, int sequence) is called on the main thread inside the NPC's think each time its AI picks a sequence for an activity (after the rules), with the activity asked for, the one after the game's weapon translation and the sequence chosen so far; it returns the sequence to use (an invalid number keeps the choice). Keep it fast and do not start animations, spawn or remove entities in it. A crash in it disables the plugin as usual (its DLL stays loaded).
Messages BLSE_MSG_ANIM_STARTED, BLSE_MSG_ANIM_ENDED, BLSE_MSG_ANIM_CHOSEN (delivered at once, inside the game's own code: the same restrictions). Behaviour as in the Python reference.
All animation functions: main thread, while a level runs; entities with a model (characters, props).
list_sequences, lookup_sequence#
int list_sequences(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);
int lookup_sequence(void *context, uint32_t entity, const char *sequence);list_sequences copies every sequence the entity's model has (its own and those of the animation models it includes), in the game's numbering, one line each: index<TAB>name<TAB>activity<TAB>loops<TAB>seconds. activity is "" if the sequence has none, loops is 1 or 0, seconds is its length (three decimals). Returns 1 or 0. (b0.5.49)
lookup_sequence returns a sequence's number by name (case ignored), or by its number written in decimal ("12"); -1 if the model has no such sequence.
play_animation, stop_animation#
int play_animation(void *context, uint32_t entity, const char *sequence, float rate, unsigned flags);
int stop_animation(void *context, uint32_t entity);Plays a sequence on an entity. (b0.5.49)
When to use it: an NPC waves, sits down, dances; a prop opens.
sequence: a name or a decimal number, as forlookup_sequence.rate: playback speed, 0 to 16 (1 normal).flags: 0, or a combination ofBLSE_ANIM_LOOP(repeat untilstop_animation) andBLSE_ANIM_STILL(the NPC stops at once, through the game'sWAIT_FOR_SCRIPTschedule started with the animation, and walks on afterwards). Other bits: the call returns 0.- An NPC keeps the animation against its own AI until it ends (once, or with
LOOPuntil stopped) or the game changes its sequence itself (death, a flinch, a conversation, a scripted scene, a task that sets an activity). Other entities just get the sequence. stop_animationends a mod's animation; 0 if none plays.
/* a sequence name taken from list_sequences for this model */
if (host->lookup_sequence(host->context, npc, "my_wave") >= 0)
host->play_animation(host->context, npc, "my_wave", 1.0f, BLSE_ANIM_STILL);animation_state#
int animation_state(void *context, uint32_t entity, BLSE_Animation *out);Fills *out with the entity's current animation. Set out->struct_size = sizeof(BLSE_Animation) first (a smaller value: 0 is returned). Returns 1 or 0. (b0.5.49)
| Field | Meaning |
|---|---|
sequence |
The sequence number playing |
activity |
That sequence's activity number, -1 none |
npc_activity |
Characters: the activity it plays now, else -1 |
ideal_activity |
Characters: the activity its AI wants, else -1 |
cycle |
How far through the sequence, 0 to 1 |
rate |
Playback rate, 1 normal |
loops, finished |
1 or 0 |
held |
1 while a mod's animation plays (play_animation) |
name, activity_name |
The sequence's and the activity's names ("" if none) |
set_animation_rule#
int set_animation_rule(void *context, const char *who, const char *activity, const char *sequences);A rule: when an NPC's AI picks a sequence for activity, use one of sequences instead. (b0.5.49)
When to use it: new idle animations for one NPC, a different walk for every cop.
who:"*"(every NPC),"#<handle>"(one entity),"model:<part of the model path>", a classname or a targetname.activity: a name such as"ACT_WALK", or its number.sequences: sequence names separated by commas or spaces; one is chosen at random each time (only those the model has).NULLor""removes the rule.- A plugin's rules end when it declines to load or is disabled after a crash. Returns 1 or 0.
/* every NPC named guard_1 idles with one of two sequences the mod added (add_model_animations) */
host->set_animation_rule(host->context, "guard_1", "ACT_IDLE", "my_idle1, my_idle2");add_animation_filter#
typedef int (BLSE_CALL *BLSE_AnimationFilterFn)(void *user, uint32_t entity, int activity, int translated, int sequence);
int add_animation_filter(void *context, BLSE_AnimationFilterFn filter, void *user);Your function sees every pick after the rules and decides the sequence. (b0.5.49) Called with the entity's handle, the activity asked for, the activity after the game's weapon translation, and the sequence chosen so far; it returns the sequence number to use (return sequence to keep the choice; an invalid number also keeps it). It runs for every NPC's activity change: keep it fast, and do not start animations, spawn or remove entities in it. A crash disables the plugin. Returns 1, or 0 if filter is NULL.
activity_id, activity_name#
int activity_id(void *context, const char *name);
int activity_name(void *context, int activity, char *buffer, size_t capacity, size_t *size);Convert between an activity's name and number. activity_id returns the number ("ACT_WALK"), or -1 if unknown. activity_name copies the name; returns 1, or 0 if there is no activity with that number. (b0.5.49)
blse.anim_started, blse.anim_ended, blse.anim_chosen#
Topics BLSE_MSG_ANIM_STARTED, BLSE_MSG_ANIM_ENDED, BLSE_MSG_ANIM_CHOSEN (b0.5.49). Delivered to native subscribers at once, inside the game's own code (the same restrictions as filters), to Python at the next safe point. BLSE installs the hooks behind them when the first subscriber appears. Every entity with a model.
blse.anim_started: an entity's sequence was (re)started. Payload, seven fields: handle (decimal), classname, name, sequence number, sequence name, the sequence's activity name (""none), the previous sequence's name (""none).blse.anim_ended: a sequence ended. The same first six fields, thenfinished(it reached its end) orinterrupted(another sequence replaced it first). A looping sequence never finishes: it ends when another one replaces it.blse.anim_chosen: an NPC's AI picked a sequence for an activity. Payload: handle, classname, name, the activity asked for, the activity after the game's translation, the sequence's name, and1if a rule or filter changed the game's choice (else0). Frequent: subscribe only while you need it.
add_model_animations, model_animations#
New animations for models (b0.5.49; check BLSE_HOST_HAS(host, model_animations)): add_model_animations(context, target_mdl, anim_mdl, flags) adds an animation-only .mdl to a model (or, for a shared animation model such as models/character/shared/male/npc_allsequences.mdl, to every model including it directly) when the engine next loads it; flags BLSE_ANIMS_REPLACE also hides the originals with the same names (renamed <name>@orig). Returns 1, 0 for invalid paths, -1 if not available on this game build. Call it from BLSE_PluginLoad (before models load); a plugin's registrations end when it declines to load or is disabled after a crash (models already loaded keep them until their next load). model_animations(context, filter, buffer, capacity, &size) copies registered<TAB>target<TAB>file<TAB>add|replace<TAB>who lines, then model<TAB>file per file added to a loaded model and model<TAB>-name per hidden sequence. Behaviour, targets and the blse/animations.txt data file as in the Python reference.
int add_model_animations(void *context, const char *target_mdl, const char *anim_mdl, uint32_t flags);
int model_animations(void *context, const char *filter, char *buffer, size_t capacity, size_t *size);When to use it: your mod ships new idles or dances for the game's characters without editing their .mdl files.
target_mdl: the model to add to (a path such as"models/character/.../name.mdl"), or a shared animation model that other models include, meaning every model that includes it directly.anim_mdl: an animation-only.mdlcompiled for the target's skeleton, found through the game's search paths, mod folders included ("models/mymod/male_idles.mdl"). Its sequences come after the model's own, so the original numbers stay.- Paths: case and
/or\do not matter. flags: 0 adds;BLSE_ANIMS_REPLACEalso hides the target's sequences with the same names, so the new ones play instead (for a shared target in every file it includes, for an exact model only in its own). Two replacers of one name: the later registration wins.model_animations: only lines whose paths containfilter(NULLor"": all). Returns 1 or 0.- Mods without code can use a
blse/animations.txtin their folder instead.
add_item_class, item_classes#
New items and weapons (b0.5.49; check BLSE_HOST_HAS(host, item_classes)): add_item_class(context, classname, template_class) makes classname (item_<name>) an item class the game can create everywhere it creates items, as template_class's C++ class (its behavior) with its own data from vdata/items/<classname>.txt, which the mod ships. Returns 1, 0 if refused (the reason goes to the plugin's log), -1 if not available on this game build. Register in BLSE_PluginLoad, before levels and saves load: a save holding the item needs the class. A plugin's classes end when it declines to load; they stay when it is disabled after a crash (they run no plugin code, and a save holding its items would crash the game on load without them). item_classes(context, buffer, capacity, &size) copies classname<TAB>template<TAB>who<TAB>created lines. Templates, files and limits as in the Python reference. New behavior for a mod's weapon (a new projectile, a fire mode) is the plugin's own hook on the template's functions (hook_function), checking the entity's classname; blse.attack, damage and hit events already name the new class.
int add_item_class(void *context, const char *classname, const char *template_class);
int item_classes(void *context, char *buffer, size_t capacity, size_t *size);classname:"item_<name>", 6-63 characters ofa-z 0-9 _("item_w_mymod_sabre"). The game creates it everywhere it creates items: maps,give/GiveItem, merchants' stock, spawning, loading saves.template_class: one of the game's item classes whose behaviour it copies ("item_w_katana": a melee weapon; a gun, a blood pack, a key, ...). 184 of the game's 202 item classes can be templates; the Python page lists the others (containers,item_g_keyring,item_w_remington_m_700,item_w_unarmed, ...).- Your mod folder ships
vdata/items/<classname>.txt: names, models, pictures, prices, damage, ammunition, sounds. - The same class again: the later registration wins.
- Returns 1; 0 if refused, with the reason in your plugin's log (
add_item_class refused: ...); -1 if not available on this game build. item_classes:createdis how many entities of the class were made this session. Mods without code can use ablse/items.txtinstead.
BLSE_EXPORT int BLSE_CALL BLSE_PluginLoad(const BLSE_HostAPI *h) {
if (BLSE_HOST_HAS(h, item_classes)) h->add_item_class(h->context, "item_w_mymod_sabre", "item_w_katana");
return 1;
}The shared database#
db_get(context, path, buffer, capacity, &size) copies a value of the database shared with Python mods (blse.db_get) as JSON text; db_set(context, path, json) sets one from JSON text (NULL removes it). Main thread. Entity values set from Python as lists or dicts come to entity_data_get as JSON text.
db_get, db_set#
int db_get(void *context, const char *path, char *buffer, size_t capacity, size_t *size);
int db_set(void *context, const char *path, const char *json);One JSON document saved with the game and shared by every mod, Python and plugins alike. (b0.5.42)
When to use it: structured data (lists, nested values) that must be in the save, or data other mods read (a Python mod's G is not readable from C; the database is).
path: where in the document.""is the whole database; keys separated by., list indexes in[ ]:"mymod.settings.speed","mymod.list[2]". Start with your mod's name.NULLis refused.db_getcopies the value as compact JSON text ({"kills":3},"text",12). Returns 1, or 0 if there is no value there.db_setparsesjsonand sets the value, making the objects on the way;jsonNULLremoves it. Values may be nested at most 64 deep. Returns 1, or 0 for invalid JSON or an invalid path.- Saved, loaded and cleared on a new game with the game's script globals; up to 1 MB. Main thread.
host->db_set(host->context, "mymod.stats", "{\"kills\": 0, \"names\": []}");
char kills[32];
if (host->db_get(host->context, "mymod.stats.kills", kills, sizeof kills, NULL)) { int k = atoi(kills); }Values on entities, finding entities, JSON files#
entity_data_set(context, entity, key, value)(text; NULL removes),entity_data_get(context, entity, key, buffer, capacity, &size),entity_data_keys(...)(each followed by\n) andentity_data_find(context, key, handles, capacity, &count): the values Python mods see withblse.entity_get(numbers set from Python come as digits). Main thread.entity_position(context, entity, origin)andfind_entities(context, center, radius, classname, flags, handles, capacity, &count), nearest first; flagsBLSE_FIND_CHARACTERS | BLSE_FIND_ALIVE | BLSE_FIND_NO_PLAYER. Main thread.json_get(context, file, path, buffer, capacity, &size)copies a value as JSON text;json_set(context, file, path, json)sets one from JSON text (NULL removes it). Any thread.
Behaviour as in the Python reference.
entity_data_set, entity_data_get, entity_data_keys, entity_data_find#
int entity_data_set(void *context, uint32_t entity, const char *key, const char *value);
int entity_data_get(void *context, uint32_t entity, const char *key, char *buffer, size_t capacity, size_t *size);
int entity_data_keys(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);
int entity_data_find(void *context, const char *key, uint32_t *handles, size_t capacity, size_t *count);Named values stored on an entity, saved with the game and shared with every mod: one mod marks an NPC, another reads the mark. (b0.5.40)
When to use it: "this NPC was already fed on", "this guard was bribed", a counter per character.
key: 1-63 characters ofA-Z a-z 0-9 _ - ., compared without regard to case. Start it with your mod's name ("mymod.fed").value: text, up to 4096 characters;NULLremoves the key. At most 256 keys per entity.entity_data_getcopies the value as text. Values set from Python come as text too: numbers as digits, lists and dicts as JSON text. Returns 1, or 0 if the entity or key does not exist.entity_data_keyscopies the entity's keys, each followed by'\n'.entity_data_findfillshandleswith this level's entities (and the player) that havekey, at mostcapacity;*countreceives how many there are (this may be more thancapacity).- Values follow the entity across saves and level changes and go with it when it is removed (killed and cleaned up, picked up, deleted). Main thread, while a level runs. All return 1 or 0.
char text[16];
int fed = 0;
if (host->entity_data_get(host->context, npc, "mymod.fed", text, sizeof text, NULL)) fed = atoi(text);
snprintf(text, sizeof text, "%d", fed + 1);
host->entity_data_set(host->context, npc, "mymod.fed", text);entity_position#
int entity_position(void *context, uint32_t entity, float origin[3]);Copies where an entity is (x, y, z in world units) into origin. Returns 1, or 0 if there is no such entity. Main thread. (b0.5.40)
find_entities#
int find_entities(void *context, const float center[3], float radius, const char *classname, unsigned flags,
uint32_t *handles, size_t capacity, size_t *count);The entities within radius units of center, nearest first. (b0.5.40)
When to use it: the NPCs near the player, every item in a room, the nearest door.
classname:""(orNULL) any class,"npc_*"a prefix, or one class; case is ignored.flags: 0 or any ofBLSE_FIND_CHARACTERS(only the player and NPCs),BLSE_FIND_ALIVE(characters that are not dead),BLSE_FIND_NO_PLAYER(leave out the player).handles: filled with up tocapacityhandles;*countreceives how many were found (at most 512).- Returns 1, or 0 if no level runs. Main thread.
float me[3];
uint32_t near[32]; size_t n = 0;
if (host->entity_position(host->context, 1, me) &&
host->find_entities(host->context, me, 400.0f, "npc_*", BLSE_FIND_ALIVE | BLSE_FIND_NO_PLAYER, near, 32, &n)) {
for (size_t i = 0; i < n && i < 32; ++i) { /* near[i] */ }
}json_get, json_set#
int json_get(void *context, const char *file, const char *path, char *buffer, size_t capacity, size_t *size);
int json_set(void *context, const char *file, const char *path, const char *json);Structured data in Bin\loader\blse\data\<file>.json, shared by every save (statistics across playthroughs, unlocks, settings with lists). (b0.5.40) Any thread.
file: a name without.json, folders separated by/("mymod/stats"), ofA-Z a-z 0-9 _ - .(not starting or ending with., not a Windows device name such ascon,nul,aux,com1).path: keys separated by., list indexes in[ ]("stats.kills","list[2].name",""the whole file). Setting the index one past the end of a list appends.json_getcopies the value as compact JSON text. Returns 1, or 0 if the file or value is missing or the file is broken.json_setsets the value from JSON text and keeps the rest of the file, making the objects on the way (and the file if needed);jsonNULLremoves the value. Values may be nested 64 deep. Returns 1, or 0 for invalid JSON, an invalid name or path, or a broken file.
Models, translated strings, frame rate#
bone_names(context, entity, buffer, capacity, &size): the bone names, each followed by\n(likelist_plugins);bone_position(context, entity, bone, origin, angles).model_scale(context, entity, &scale, &parts)andset_model_scale(context, entity, scale, parts): the scale (0.05-8) and the parts scaled with it,BLSE_SCALE_VIEW | BLSE_SCALE_HITBOXES | BLSE_SCALE_COLLISION(0: the picture only), and how long (b0.5.39):BLSE_SCALE_KEEP(applied again after loads and level changes); without it: until the level ends.translate(context, mod, key, buffer, capacity, &size)andtranslation_language(context, buffer, capacity, &size).frame_stats(context, &stats)withstats.struct_size = sizeof(BLSE_FrameStats).
Entities are indexes (below 8192) or handles. Models: main thread while a level runs; translations and frame figures: any thread. Behaviour and limits as in the Python reference.
bone_names, bone_position#
int bone_names(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);
int bone_position(void *context, uint32_t entity, const char *bone, float origin[3], float angles[3]);bone_names copies the model's bone names in the model's order, each followed by '\n' ("Bip01\nBip01 Pelvis\n..."). bone_position copies where one bone is in the world (origin) and how it is turned (angles: pitch, yaw, roll); bone is matched without regard to case; neither array may be NULL. Both return 1, or 0 for an entity without a model or an unknown bone. Main thread, while a level runs; entities with a model (characters, props, items lying in the world). (b0.5.38)
When to use them: put an effect on an NPC's head, aim at a hand.
model_scale, set_model_scale#
int model_scale(void *context, uint32_t entity, float *scale, unsigned *parts);
int set_model_scale(void *context, uint32_t entity, float scale, unsigned parts);Draw a model larger or smaller. (b0.5.38; BLSE_SCALE_KEEP b0.5.39)
scale: 0.05 to 8 (1 normal).model_scalestores the current scale (1normal,0never set, drawn as 1);scalemust not beNULL,partsmay be.parts: what is scaled besides the picture, any of:
| Bit | Characters | The player |
|---|---|---|
BLSE_SCALE_VIEW |
The eye height (where they see and aim from) | The eye height, standing and crouched |
BLSE_SCALE_HITBOXES |
The bones hits, attachments and bone_position use |
The same |
BLSE_SCALE_COLLISION |
The box others bump into | The box and the hulls the player moves with |
0 scales only the picture.
- How long: without
BLSE_SCALE_KEEPuntil the level ends (saves hold the entity at the size it had before, and the parts end). WithBLSE_SCALE_KEEPthe scale and its parts are kept in the save and applied again after a load, when the level is entered again and (the player) after every level change. Calling it again withoutKEEPends a kept scale. - NPCs still find their paths as normal-sized characters: a scaled-up NPC with
COLLISIONcan get stuck in doorways. Scale its look, view and hit boxes withoutCOLLISIONwhen it has to walk around. - Return 1 or 0. Main thread, while a level runs.
host->set_model_scale(host->context, giant, 1.8f, BLSE_SCALE_VIEW | BLSE_SCALE_HITBOXES | BLSE_SCALE_COLLISION | BLSE_SCALE_KEEP);translate, translation_language#
int translate(void *context, const char *mod, const char *key, char *buffer, size_t capacity, size_t *size);
int translation_language(void *context, char *buffer, size_t capacity, size_t *size);Text in the player's language. (b0.5.38) Any thread.
translatecopies the text forkey(with or without a leading$) fromBin\loader\blse\translations\<mod>_<language>.txt, else<mod>_english.txt, else the key itself (UTF-8). Returns 1, or 0 only for an invalid mod name or key.translation_languagecopies the language translations use ("english","german", ...).- For text you hand to BLSE's interface functions you do not need these (they translate
$KEYtext themselves); seetranslate_textin Translated interface text. File format: Translated strings.
frame_stats#
int frame_stats(void *context, BLSE_FrameStats *out);Frame-rate figures over the last second, measured at the end of every frame the game draws. (b0.5.38) Any thread. Set out->struct_size = sizeof(BLSE_FrameStats) first. Returns 1, or 0 until two frames were drawn (or with a smaller struct_size).
| Field | Meaning |
|---|---|
fps |
Frames a second over the last second |
last_ms |
The last frame's time, in milliseconds |
average_ms |
The average frame time over the last second |
worst_ms |
The longest frame over the last second |
frames |
Frames drawn in the last second |
BLSE_FrameStats st = { sizeof(BLSE_FrameStats) };
if (host->frame_stats(host->context, &st) && st.worst_ms > 50.0) host->log("a slow frame");Appearance#
b0.5.47; behaviour and limits as in the Python reference. Main thread while a level runs; each returns 1 or 0.
- Bones:
bone_transform(context, entity, bone, &t)andset_bone_transform(context, entity, bone, &t)with aBLSE_BoneTransform(scale[3],offset[3],angles[3],flags:BLSE_BONE_HITBOXES | BLSE_BONE_ALONE | BLSE_BONE_KEEP); an identity transform removes the modifier.reset_bones(context, entity, bone)(NULL: all);modified_bones(context, entity, buffer, capacity, &size). - Faces:
flex_names(...)(\nafter each),flex_value(context, entity, name, &value, &minimum, &maximum, &held),set_flex(context, entity, name, value, flags)(BLSE_FLEX_KEEP),release_flex(context, entity, name)(NULL: all). - Skins, body groups, colour:
entity_skin(context, entity, &skin, &count),set_entity_skin(context, entity, skin, flags)(BLSE_LOOK_FADE | BLSE_LOOK_KEEP),body_groups(...)("name\tcount\tvalue\n"per group),set_body_group(context, entity, group, value, flags),render_color(context, entity, rgba, &mode, &fx),set_render_color(context, entity, r, g, b, a, flags)andset_render_mode(context, entity, mode, fx, flags)(-1 keeps a value). - Materials:
set_entity_material(context, entity, original, replacement, flags)(""hides, NULL removes; both NULL: all of the entity's),model_materials(...)andentity_materials(...)("original\treplacement\n").
Each entry below returns 0 also on builds where the feature is unavailable. entity: an index (1 = the player) or a handle.
How long changes last. By default a change lasts until the entity is gone or the level ends. The *_KEEP flags (BLSE_BONE_KEEP, BLSE_FLEX_KEEP, BLSE_LOOK_KEEP, all the value 8) keep it in the save and apply it again after loads and level changes (the player's on every level). Skins, body groups and colour are also saved by the game itself; KEEP applies them again for example after a script gave the character another model.
bone_transform, set_bone_transform, reset_bones, modified_bones#
typedef struct BLSE_BoneTransform {
float scale[3]; /* 0.05 to 8 along the bone's axes; 1 1 1 unchanged */
float offset[3]; /* model units in the bone's axes, -256 to 256 */
float angles[3]; /* pitch, yaw, roll in degrees, -360 to 360 */
unsigned flags; /* BLSE_BONE_* */
} BLSE_BoneTransform;
int bone_transform(void *context, uint32_t entity, const char *bone, BLSE_BoneTransform *transform);
int set_bone_transform(void *context, uint32_t entity, const char *bone, const BLSE_BoneTransform *transform);
int reset_bones(void *context, uint32_t entity, const char *bone);
int modified_bones(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);A modifier per bone, applied in the bone's own space after the game built the skeleton: make a head bigger, bend an arm, move a hand. (b0.5.47)
bone: the bone's name (seebone_names); must not beNULLfor the first two.flags:BLSE_BONE_HITBOXESalso moves the bones hits, attachments andbone_positionuse;BLSE_BONE_ALONEmoves only this bone (its children normally follow);BLSE_BONE_KEEPkept in the save.set_bone_transformsets the bone's whole modifier; scale 1 1 1 with offset and angles 0 removes it.bone_transformreads it (an identity transform if none).reset_bonesremoves one bone's modifier, or all of the entity's withboneNULL.modified_boneslists the bones with modifiers, each followed by'\n'.
BLSE_BoneTransform t = { {1.5f, 1.5f, 1.5f}, {0, 0, 0}, {0, 0, 0}, BLSE_BONE_HITBOXES };
host->set_bone_transform(host->context, npc, "Bip01 Head", &t);flex_names, flex_value, set_flex, release_flex#
int flex_names(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);
int flex_value(void *context, uint32_t entity, const char *name, float *value, float *minimum, float *maximum, int *held);
int set_flex(void *context, uint32_t entity, const char *name, float value, unsigned flags);
int release_flex(void *context, uint32_t entity, const char *name);Facial expressions. (b0.5.47)
flex_nameslists the model's flex controllers, each followed by'\n'.flex_valuereads one in its own units, with its range and whether BLSE holds it (*held1). Any out pointer may beNULL.set_flexholds a controller atvalue(clamped to its range; sent to the client in 64 steps) against the game's own expressions untilrelease_flex, the entity is gone or the level ends.BLSE_FLEX_KEEP: kept in the save. Blinking and lip sync still play on top.release_flexgives one back to the game, or all withnameNULL.
entity_skin, set_entity_skin#
int entity_skin(void *context, uint32_t entity, int *skin, int *count);
int set_entity_skin(void *context, uint32_t entity, int skin, unsigned flags);The model's skin (its set of textures). entity_skin stores the current skin in *skin (must not be NULL) and how many the model has in *count (may be NULL). set_entity_skin sets one, 0 to count - 1; flags: BLSE_LOOK_FADE uses the game's cross-fade instead of switching at once, BLSE_LOOK_KEEP keeps it. (b0.5.47)
body_groups, set_body_group#
int body_groups(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);
int set_body_group(void *context, uint32_t entity, const char *group, int value, unsigned flags);body_groups copies one line per group: name<TAB>count<TAB>value (how many sub-models it has and the one shown). set_body_group shows sub-model value (0 to count - 1) of a group, by name; flags BLSE_LOOK_KEEP. Bloodlines' own character models have few body groups. (b0.5.47)
render_color, set_render_color, set_render_mode#
int render_color(void *context, uint32_t entity, int rgba[4], int *mode, int *fx);
int set_render_color(void *context, uint32_t entity, int r, int g, int b, int a, unsigned flags);
int set_render_mode(void *context, uint32_t entity, int mode, int fx, unsigned flags);Tint and fade a model. (b0.5.47)
render_colorcopies red, green, blue, alpha (0-255) intorgba(must not beNULL) and the render mode and effect into*modeand*fx(either may beNULL).set_render_color: 255 255 255 is no tint; -1 keeps a value. An alpha below 255 fades the entity: BLSE draws it see-through until it is gone or the level ends (withBLSE_LOOK_KEEPalso after loads). WithoutKEEPthe alpha value itself stays on the entity (the game saves it) while the entity is drawn solid again after a load: set it back to 255 when the fade should end.set_render_mode: the game's render mode and render effect numbers, 0-255; -1 keeps a value.- Colour and mode work on any entity, also ones without a model.
host->set_render_color(host->context, npc, 255, 64, 64, 128, 0); /* red and half see-through */set_entity_material, model_materials, entity_materials#
int set_entity_material(void *context, uint32_t entity, const char *original, const char *replacement, unsigned flags);
int model_materials(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);
int entity_materials(void *context, uint32_t entity, char *buffer, size_t capacity, size_t *size);One entity drawn with other materials, without changing other entities with the same model (drawing only). (b0.5.47)
original: a material of the model, by its path (frommodel_materials) or just its name in the model.replacement: a material path withoutmaterials/and.vmt(a.vmtyour mod ships, not a bare texture);""hides those meshes;NULLremoves the replacement (withoriginalNULLtoo: all of the entity's).flags:BLSE_LOOK_KEEPkeeps it in the save.model_materialslists the model's material paths, each followed by'\n'.entity_materialscopiesoriginal<TAB>replacementlines for each replacement BLSE draws.
Player outfits#
Outfits are clothes the player wears whatever armor is equipped; the armor keeps its protection. The whole idea, the rules for model files and the exact behaviour are in the Python reference: read its "Read this first" list before you start. Plugins have the same functions as Python, as six BLSE_HostAPI fields (added in 0.6 after item_classes; check host->struct_size with BLSE_HOST_HAS as for any newer field: api_version does not change when fields are added). Mods without code can use a blse/outfits.txt file instead.
Why BLSE is needed for this: the game picks the player's model from clan, gender and armor every time a weapon or armor is taken out or put away (also when first and third person change), so a plain model change does not last. BLSE keeps one player model slot, filled by an outfit or a single model, and puts that model back whenever the game picks the clan model. Scripts' and cutscenes' own models and the Protean beast form pass unchanged.
All six return 1 on success, 0 if refused (the reason is written to blse.log, attributed to your plugin) and -1 where noted if outfits are not available on this game version. Call all six on the game's main thread (BLSE_PluginLoad, a command, an event handler or add_task); from another thread they return 0. Text results use the usual buffer, capacity, &size pattern: size receives the full length, so a call with a too-small buffer tells you how much to allocate.
add_outfit(context, id, fields): registers an outfit, the same as an [id] section of outfits.txt.
id: the outfit's name for later calls, 1 to 63 ofa-z 0-9 _(upper case is lowered).fields: the outfit's keys, onekey=valueper line (\nbetween lines):name,male,female,male.<clan>,female.<clan>,skin,body.<group>,material.<original>. At least one model.- Returns 1, 0 (bad id, key or value, or no model), -1 (not available). The same id again replaces the outfit (logged); the later one wins.
- Register at plugin load, so outfits worn in a save exist when it loads. Your outfits are removed when your plugin declines to load; a worn one is then taken off. They stay when it is disabled after a crash (they run no plugin code, and the player keeps the clothes on).
wear_outfit(context, id): puts the outfit on the player; id NULL or "" takes off whatever BLSE put on (the game's clan and armor model comes back).
- Returns 1 (worn, or will be as soon as the level or the beast form allows), 0 (unknown id, no model for the player's gender or clan, a missing file, a model of the other gender), -1 (not available). Kept in the save.
worn_outfit(context, buffer, capacity, &size): the worn outfit's id, "" if none.
list_outfits(context, buffer, capacity, &size): every registered outfit, one line each: id\twho\tname\tmale\tfemale\n (who: mod <folder>, python or the plugin's name; empty fields for what was not given).
set_player_model(context, model, options): one model on the player without an outfit (a disguise); it survives weapon and armor changes like an outfit.
model:"models/....mdl";NULLor""removes it.options:0lasts until the level ends;BLSE_LOOK_KEEPkeeps it in the save until removed.- Returns 1, 0, -1 as
wear_outfit. Wearing an outfit replaces it and the other way round.
player_model(context, buffer, capacity, &size): the model the player has right now (the game's, an outfit's, a cutscene's); "" while no level runs.
The event BLSE_MSG_OUTFIT_CHANGED ("blse.outfit_changed"): sent when what the player wears from BLSE changes. Payload, four NUL-separated texts: the outfit now ("" none, "*" a set_player_model model), the one before, the model BLSE put on ("" the game's own), and why: wear, remove, load, missing, level, unregistered (meanings in the Python reference).
The reasons in short: wear an outfit or model was put on; remove it was taken off; load worn again from a save; missing a save's or the worn outfit's model or registration is gone, so the game's model is back; level a model set without BLSE_LOOK_KEEP ended with the level; unregistered the plugin that registered the outfit went away.
The C signatures:
int add_outfit(void *context, const char *id, const char *fields);
int wear_outfit(void *context, const char *id);
int worn_outfit(void *context, char *buffer, size_t capacity, size_t *size);
int list_outfits(void *context, char *buffer, size_t capacity, size_t *size);
int set_player_model(void *context, const char *model, unsigned options);
int player_model(void *context, char *buffer, size_t capacity, size_t *size);worn_outfit, list_outfits and player_model return 1 (the text may be empty), or 0 if called off the main thread.
/* At plugin load: one outfit, and a console command that puts it on. */
static const BLSE_HostAPI *host;
static void BLSE_CALL wear_cmd(int argc, const char *const *argv, const char *args, void *user) {
if (host->wear_outfit(host->context, "my_suit") != 1)
host->console_print(host->context, "the suit cannot be worn now (see blse.log)\n");
}
BLSE_EXPORT int BLSE_CALL BLSE_PluginLoad(const BLSE_HostAPI *h) { /* in a .cpp file: extern "C" BLSE_EXPORT ... */
host = h;
if (h->struct_size < offsetof(BLSE_HostAPI, player_model) + sizeof(h->player_model)) return 0; /* BLSE too old */
h->add_outfit(h->context, "my_suit",
"name=Black suit\n"
"male=models/my_mod/suit_m.mdl\n"
"female=models/my_mod/suit_f.mdl\n");
h->add_command(h->context, "wear_suit", "Puts on the black suit", wear_cmd, NULL);
return 1;
}(BLSE_HOST_HAS(h, player_model) is the short form of that struct_size check.)
Camera and sounds#
camera_get(context, &third_person, &fov): 1 or 0 (-1 if unknown) and the field of view in degrees (0 is the game's default, -1 outside a level).camera_set(context, third_person, fov): switches first/third person through the game's toggle and sets the field of view (0 restores the default); -1 keeps either. 1 if everything asked was done.play_sound(context, file, volume, pitch)andstop_sound(context, file): a sound belowsound/, heard by the player without position.play_ui_sound(context, file, volume, pitch)(b0.5.49): an interface sound, played the way the game's main menu plays its own (also with no level loaded). Button sounds: see Interface.- b0.5.31:
camera_view(context, origin, angles)(float[3] each); b0.5.32:camera_override(context, origin, angles)(either may be NULL to keep the game's; both NULL: the game's view again; it also ends with the level and when the plugin is disabled or unloaded);play_sound_at(context, file, origin, volume, pitch, attenuation),play_sound_on(context, file, entity, volume, pitch, attenuation)(an entity handle or index),stop_sound_on(context, file, entity);sound_length(context, file, &seconds).
See the Python reference.
All camera and sound functions: main thread.
camera_get, camera_set#
int camera_get(void *context, int *third_person, int *fov);
int camera_set(void *context, int third_person, int fov);First or third person, and the field of view. (b0.5.30)
camera_getstores*third_person(1 third person, 0 first person, -1 unknown) and*fov(degrees; 0 means the game's default; -1 outside a level). Either pointer may beNULL. Returns 1 if either value is known, else 0.camera_set:third_person0 or 1 switches through the game's own toggle, -1 keeps it;fov1 to 179 sets the field of view, 0 restores the default, -1 keeps it. Returns 1 if everything asked was done.
host->camera_set(host->context, 1, -1); /* third person, field of view unchanged */camera_view, camera_override#
int camera_view(void *context, float origin[3], float angles[3]);
int camera_override(void *context, const float *origin, const float *angles);camera_view (b0.5.31) copies the position and angles (pitch, yaw, roll) the last frame was drawn from; both arrays must be given. Returns 0 outside a level.
camera_override (b0.5.32) draws the game from another place (a cutscene camera, a security monitor) while the player still moves and aims as before.
origin,angles: the view to use;NULLkeeps the game's for that one.- Both
NULLends your plugin's override (another plugin's stays). It also ends when the level ends or your plugin is disabled or unloaded. - Returns 1 or 0.
const float eye[3] = {0.0f, 0.0f, 512.0f}, down[3] = {90.0f, 0.0f, 0.0f};
host->camera_override(host->context, eye, down); /* look straight down from above */
/* later */ host->camera_override(host->context, NULL, NULL);play_sound, stop_sound, play_ui_sound#
int play_sound(void *context, const char *file, float volume, int pitch);
int stop_sound(void *context, const char *file);
int play_ui_sound(void *context, const char *file, float volume, int pitch);Sounds the player hears without a position (b0.5.30; play_ui_sound b0.5.49).
file: a path belowsound/("player/pl_wade2.wav").volume: 0 to 1.pitch: 100 normal (higher is faster and higher).play_ui_soundplays an interface sound the way the game's main menu plays its own: also on the main menu with no level loaded and over the paused Esc menu. Sounds for buttons: Interface.- Return 1 if the engine took the request.
play_sound_at, play_sound_on, stop_sound_on, sound_length#
int play_sound_at(void *context, const char *file, const float origin[3], float volume, int pitch, float attenuation);
int play_sound_on(void *context, const char *file, uint32_t entity, float volume, int pitch, float attenuation);
int stop_sound_on(void *context, const char *file, uint32_t entity);
int sound_length(void *context, const char *file, float *seconds);Sounds with a place in the world. (b0.5.31)
play_sound_atplays from a point;play_sound_onfrom an entity (index or handle), following it as it moves;stop_sound_onstops one on an entity.attenuation: how quickly it gets quieter with distance; 0.8 is normal, 0 is heard everywhere.sound_lengthstores the file's length in seconds, read from its WAV or MP3 header. Returns 1, or 0 (and 0 seconds) if unknown.
float secs = 0;
host->play_sound_on(host->context, "player/pl_wade2.wav", npc, 1.0f, 100, 0.8f);
if (host->sound_length(host->context, "player/pl_wade2.wav", &secs)) { /* secs */ }Mod folders and file filters#
(b0.5.48) Behaviour and limits as in the Python reference. Main thread; each returns 1 or 0.
provide_file(context, path, data, size): the game reads these bytes as the game filepath("vdata/items/x.txt") from now on, over the game's and the mods' files (merge files still apply);dataNULL removes it.add_file_filter(context, path, filter, user):filter(user, path, data, size, set, set_context)is called when a game file matchingpath(*and?allowed) is built, on the main thread (a matching file the game first opens on another thread is built without filters; logged). Keeppathnarrow: each matching file is copied intoblse/cache/files. To change the file it callsset(set_context, new_data, new_size)before returning; not calling it keeps the file as it is.add_map_filter(context, filter, user): the same for a map's entity text, called with the map's name when the server parses it (a new game, themapcommand, a map's first visit; not a loaded save or a revisited map).list_mods(context, buffer, capacity, &size):"name\tfolder\n"for each enabled mod folder, in load order (the last one wins).mod_file_owner(context, path, buffer, capacity, &size): the mod providing a game file,"(merged)"or"(provided)"; 0 if none.- (b0.5.48)
mod_info(ctx, folder, buffer, capacity, size): one mod folder, loaded or not, askey\tvaluelines:folder,name,version,author,description,requires,requires_blse(itsmod.ini),position(0 if not loaded),loaded,reason. Conflicts follow one rule: the mod later inloadorder.txtwins (the Python page).
Filters run inside BLSE's crash guard like dialogue filters: a filter that crashes disables its plugin. The game keeps many files after reading them once, so register filters in BLSE_PluginLoad.
A mod folder is a folder of game files (models, sounds, vdata item files, maps) that BLSE puts over the game's own; see A mod folder. A merge file adds to a data file instead of replacing it. The game keeps many files it read once: a change reaches only files it opens later.
static void BLSE_CALL add_sound_entry(void *user, const char *path, const char *data, size_t size, BLSE_SetBytesFn set, void *set_context) {
/* e.g. append a line to a sound script */
static const char extra[] = "\r\n\"my.sound\" { \"wave\" \"my_mod/hit.wav\" }\r\n";
char *out = (char *)malloc(size + sizeof(extra));
if (!out) return;
memcpy(out, data, size);
memcpy(out + size, extra, sizeof(extra) - 1);
set(set_context, out, size + sizeof(extra) - 1); /* BLSE copies the bytes */
free(out);
}
/* in BLSE_PluginLoad: */
if (BLSE_HOST_HAS(host, add_file_filter)) host->add_file_filter(host->context, "scripts/game_sounds_*.txt", add_sound_entry, NULL);provide_file#
int provide_file(void *context, const char *path, const void *data, size_t size);The game reads size bytes from data as the game file path from now on, over the game's and the mods' own files (merge files still apply to it). BLSE copies the bytes. data NULL removes your file again. (b0.5.48)
When to use it: data you build at run time (an item file from your settings, a generated sound script).
path: a game path with/("vdata/items/item_w_mymod_sabre.txt").- Returns 1 or 0.
mod_file_ownerthen reports"(provided)"for the path.
add_file_filter, add_map_filter#
typedef void (BLSE_CALL *BLSE_SetBytesFn)(void *set_context, const char *data, size_t size);
typedef void (BLSE_CALL *BLSE_TextFilterFn)(void *user, const char *name, const char *data, size_t size,
BLSE_SetBytesFn set, void *set_context);
int add_file_filter(void *context, const char *path, BLSE_TextFilterFn filter, void *user);
int add_map_filter(void *context, BLSE_TextFilterFn filter, void *user);Change a game file, or a map's entities, while the game loads it. (b0.5.48)
When to use them: add a line to a sound script, change a value in an item file, add an NPC to a map, remove a locked door.
path(add_file_filter): the files to filter,*(any characters) and?(one) allowed. Keep it narrow: each matching file is copied intoblse/cache/files.- Your
filteris called on the main thread with youruservalue,name(the file's path, or for a map filter the map's name), and the current bytesdata,size(after the mods and merges; borrowed, not NUL-terminated). - To change the content, build the new bytes and call
set(set_context, new_data, new_size)before you return; BLSE copies them. Not callingsetkeeps the content as it is.setwithNULLand 0 makes the file empty. - File filters run when a matching file is built; a matching file the game first opens on another thread is built without filters (this is logged).
- Map filters get a map's entity text when the server parses it: a new game, the
mapcommand, the first visit of a map. Not a loaded save or a revisited map (those come from the save). - Filters run inside BLSE's crash guard: a crash disables your plugin. Register them in
BLSE_PluginLoad. - Return 1, or 0 if an argument is missing or invalid.
list_mods, mod_file_owner, mod_info#
int list_mods(void *context, char *buffer, size_t capacity, size_t *size);
int mod_file_owner(void *context, const char *path, char *buffer, size_t capacity, size_t *size);
int mod_info(void *context, const char *folder, char *buffer, size_t capacity, size_t *size);Information about the installed mod folders. (b0.5.48)
list_modscopiesname<TAB>folderlines for each enabled mod folder, in load order: the last one wins conflicts.mod_file_ownercopies the name of the mod providing the game filepath, or"(merged)"(built from merge files) or"(provided)"(fromprovide_file). Returns 0 if no mod provides it (the game's own file).mod_infocopies one mod folder's details, loaded or not, askey<TAB>valuelines:folder,name,version,author,description,requires,requires_blse(from itsmod.ini),position(1 = lowest priority, 0 if not loaded),loaded(1 or 0),reason(why it is not loaded). Returns 0 if there is no such folder.
Companions#
Behaviour as in the Python reference. Main thread while a level runs; 1 or 0.
set_companion(context, npc, on, options):on1 makes the NPC a companion (it goes with the player through level changes, with its whole state), 0 ends it.options: NULL or words:"follow","stay","range=<units>". Kept in the save.list_companions(context, all_maps, buffer, capacity, &size):"handle\tmap\tclass\tname\toptions\n"per companion of this level;all_maps1 adds those left on other maps (empty handle).block_companion_map(context, map, block): no companion goes to that map while blocked; not saved.- Events:
BLSE_MSG_COMPANION(what, handle, class, name, map;whatisarrived,stayedorlost) andBLSE_MSG_HIT(every hit with the victim's and attacker's handles).
A companion is an NPC that goes with the player through level changes with its whole saved state: the game carries it as it carries the player's items, with its inventory, BLSE entity values and kept looks. (b0.5.48)
set_companion#
int set_companion(void *context, uint32_t npc, int on, const char *options);npc: an index or handle.on1 makes it a companion, 0 ends it.options:NULLor""for none, else words separated by spaces:"follow"(BLSE callsfollow_npc(npc, player)again on every level and after loads),"stay"(registered, but does not go along),"range=<units>"(goes along only when within that distance of the player).- Kept in the save. Returns 1 or 0.
host->set_companion(host->context, npc, 1, "follow range=1500");list_companions#
int list_companions(void *context, int all_maps, char *buffer, size_t capacity, size_t *size);Copies handle<TAB>map<TAB>class<TAB>name<TAB>options lines for each companion of this level. all_maps 1 also lists those left on other maps (their handle field is empty). Returns 1 or 0.
block_companion_map#
int block_companion_map(void *context, const char *map, int block);block 1: no companion goes to that map (a cutscene map, a place it must not be); 0 ends the block. Not saved. Returns 1 or 0.
blse.companion#
Topic BLSE_MSG_COMPANION. Payload, five fields: what happened, the NPC's handle (decimal; empty when it is not on this level), classname, name, a map. What happened:
arrived: it came along from the map given;stayed: it stayed on the map given (stay, a blocked map, out of range, or dead);lost: it died or was removed; it is no longer a companion.
Delivered to native subscribers at once, to Python at the next safe point. For hits between companions and enemies see blse.hit.