Engine access and UI¶
BML+ script mods run in CKAngelScript. Use the owner of each operation instead of building overlapping wrappers:
- BML+ owns mod identity, load order, callbacks, config, commands, resources, loader UI, built-in services, and DataShare.
- CKAngelScript owns scene and behavior APIs, runtime scripts, components, messages, async work, and registered CK/Vx bindings.
- A native plugin owns unsafe hooks, native memory, performance-critical loops, and plugin-specific CKAngelScript extensions.
Choose the CKAngelScript surface¶
| Goal | Surface |
|---|---|
| Find or change scene objects, data arrays, materials, meshes, textures, cameras, or scene membership | CKAS Scene and CK/Vx APIs, coordinated by a BML+ script mod when mod services are also needed |
| Add logic to one behavior instance | AngelScript Component |
| Search or edit behavior graphs and Building Blocks | CKAS Behavior, BB, and Param |
| Communicate among CKAS runtime scripts or components | CKAS Message; use Async only from an execution context that permits suspension |
| Add BML+ config, commands, resources, lifecycle, or loader UI | BML+ script mod APIs |
| Patch engine internals or expose a native service | Native plugin with a guarded CKAS extension if scripts need it |
BML+ callbacks receive BML::ModContext, not ScriptContext or
CKBehaviorContext. Use ctx.BorrowCKContext() only with APIs that accept a
CKContext@. Do not fake one context as another.
Raw NativePointer, NativeBuffer, DynCall/DynLoad/DynCallback, and writable
native memory are escape hatches for proven native integration work. They are
not a normal replacement for a missing plugin API.
CK objects and durable identity¶
Start with CKAngelScript Scene, Behavior, BB, and Param. BML+
ctx.Borrow* calls provide convenient access to named CK objects and managers,
but those handles are non-owning.
Prefer CKAS references for identity that crosses callbacks:
Entity3DRef@ ball;
void OnLoad(const BML::ModContext &in ctx) {
CKContext@ ck = ctx.BorrowCKContext();
@ball = Scene::FindEntity3D(ck, "Ball");
}
void OnProcess(const BML::ModContext &in ctx) {
if (ball is null || !ball.valid)
return;
CK3dEntity@ entity = ball.Entity3D();
if (entity !is null)
entity.Translate(VxVector(0.0f, 0.0f, 0.1f));
}
Resolve raw CK handles close to the operation. Revalidate after deletion, level changes, and CK reset.
BML::CK contains small stateless helpers, not a second complete Virtools API:
void OnLoad(const BML::ModContext &in ctx) {
CKDataArray@ array = ctx.BorrowDataArrayByName("Some_Array");
int column = BML::CK::FindColumn(array, "Name");
string value = BML::CK::GetString(array, 0, column, "");
BML::ObjectLoadOptions options;
options.File = "3D Entities\\Example.nmo";
BML::ObjectLoadResult@ loaded = BML::CK::LoadObject(options);
CKObject@ main = loaded is null ? null : loaded.BorrowMainObject();
}
Content registration¶
RegisterBallType, RegisterFloorType, and RegisterModule expose common
Ballance content registration. Call them from OnLoad; late registration
returns false.
void OnLoad(const BML::ModContext &in ctx) {
BML::BallTypeDefinition ball;
ball.BallId = "example.ball";
ball.BallName = "Example Ball";
ball.BallFile = "Resources/example_ball.nmo";
ball.ObjectName = "Example_Ball";
ball.Friction = 0.4f;
ball.Elasticity = 0.2f;
ball.Mass = 1.0f;
ball.Radius = 2.0f;
if (!ctx.RegisterBallType(ball))
ctx.LogWarn("failed to register ball type");
}
Definitions are value objects. Keep asset paths mod-relative.
Hook Block¶
Hook Block inserts a callback at a known point in an existing Virtools behavior graph. CKAngelScript should find the owner script and Building Blocks; BML+ owns the inserted native block and retained callback.
BML::HookBlockRef@ hook;
int OnBallHook(const BML::ModContext &in ctx,
const BML::HookBlockEvent &in event) {
ctx.LogInfo("Hooked " + event.BlockName);
return CKBR_OK;
}
void OnLoad(const BML::ModContext &in ctx) {
CKBehavior@ owner = ctx.BorrowScriptByName("Gameplay_Ingame");
CKBehavior@ source = FindSubBehaviorByName(owner, "Some_Behavior");
if (owner is null || source is null)
return;
@hook = ctx.InsertHookBlockAfter(owner, source, OnBallHook, "example hook");
}
CreateHookBlock creates an unattached block. InsertHookBlockAfter,
InsertHookBlockBefore, and InsertHookBlockBetween cover common one-input,
one-output patches. If several links match, find a more specific source or
target before insertion.
By default the block activates all outputs after the callback. Set
AutoActivateOutputs to false when the callback selects a branch through
event.ActivateOutput(index).
BML+ removes installed hooks at unload. Store HookBlockRef@ only when the mod
needs to disable or uninstall a hook earlier. Do not store borrowed behavior
handles from a Hook Block event.
Physics and text helpers¶
BML::Physics wraps BML+'s runtime physics actions. Calls return false when
no level is loaded or the target is null. It does not expose private PhysicsRT
manager types.
void MakeBallPhysical(const BML::ModContext &in ctx, CK3dEntity@ target) {
BML::PhysicalizeDefinition def;
def.Fixed = false;
def.Friction = 0.4f;
def.Elasticity = 0.2f;
def.Mass = 1.0f;
def.CollisionGroup = "Ball";
def.EnableCollision = true;
if (!BML::Physics::PhysicalizeBall(
target, def, VxVector(0.0f, 0.0f, 0.0f), 2.0f))
ctx.LogWarn("physicalize failed");
}
BML::Text creates a 2D Text behavior under an owner script and returns a
borrowed behavior handle. Keep Text2DDefinition value-only and pass required
materials explicitly.
BML UI¶
BML::UI contains two kinds of operation:
- messages, menu state, and HUD commands that can run from normal callbacks;
- drawing controls that require the active ImGui frame and must run from
OnProcess.
bool enabled = true;
int count = 3;
string search = "";
void OnProcess(const BML::ModContext &in ctx) {
BML::UI::Title("Example Script");
BML::UI::WrappedText("BML controls", 360.0f);
if (BML::UI::MainButton("Click"))
ctx.LogInfo("clicked");
BML::UI::YesNoButton("Enabled", enabled);
BML::UI::InputIntButton("Count", count);
BML::UI::SearchBar(search);
}
Calls made outside an active frame return defaults or draw nothing. This guard prevents host corruption; it does not make the call correct.
Advanced ImGui¶
Use ImGui for custom windows, tables, trees, images, and frame-local draw-list
work. Use it only from OnProcess. Handles such as ImDrawList@, ImGuiIO@,
and ImGuiStyle@ are valid only for the current frame.
string filter = "";
void OnProcess(const BML::ModContext &in ctx) {
if (ImGui::Begin("Advanced ImGui")) {
ImGui::TextUnformatted("Custom script UI");
ImGui::InputText("Filter", filter, 128);
}
ImGui::End();
}
Use BML+ timers for delayed work from a BML+ mod. BML+ callbacks are no-suspend, so they cannot await CKAngelScript async work.
Next: Communication.