BML services for script mods¶
BML::ModContext is the per-mod entry point for logging, configuration,
resources, input, commands, timers, mod lookup, and loader-owned services.
Prefer the callback parameter over a global helper when both exist.
ModContext¶
void OnLoad(const BML::ModContext &in ctx) {
ctx.LogInfo("id=" + ctx.ModId);
BML::Config@ config = ctx.BorrowConfig();
if (config !is null) {
BML::ConfigProperty@ greeting = config.GetProperty("General", "Greeting");
if (greeting !is null)
greeting.SetDefaultString("hello");
}
}
Common capabilities include:
- the current mod id, root, logger, and configuration;
- BML/game state, directories, input, HUD, messages, and menus;
FindMod,GetModCount, andGetMod;- Timer and Command registration;
- DataShare reads and requests;
- CK manager and named-object borrowing;
- typed runtime, gameplay, and event snapshots.
Use LogInfo, LogWarn, and LogError for ordinary per-Mod logging without
creating a handle. BorrowLogger() remains available when code specifically
needs a revalidating logger wrapper. BorrowConfig() returns a revalidating
BML+ wrapper. Other Borrow* methods commonly expose non-owning CK or manager
handles; do not keep those across callbacks.
Resources and paths¶
Resolve files through the mod root so directory, single-file, and zip packages behave consistently:
void OnLoad(const BML::ModContext &in ctx) {
string root = ctx.GetModRootUtf8();
string text = ctx.ReadModTextFileUtf8("Resources/readme.txt", "");
if (ctx.ModFileExistsUtf8("Resources/settings.txt"))
ctx.LogInfo("resource root=" + root + " text=" + text);
}
ResolveModPathUtf8, ModFileExistsUtf8, ModDirectoryExistsUtf8, and
ReadModTextFileUtf8 guard access to mod resources. BML::Path provides pure
path operations such as Combine, Normalize, FileName, Extension,
RemoveExtension, IsAbsolute, IsRelative, Exists, IsFile, and
IsDirectory.
Known BML+ paths are available through GetDirectoryUtf8, including
DIR_GAME, DIR_LOADER, DIR_CONFIG, and DIR_TEMP. Prefer mod-relative
resource paths for publishable assets.
Logging and configuration¶
Logger, Config, and ConfigProperty expose IsValid. Stored wrapper
handles remain usable only while their owning mod and property remain valid.
BML::ConfigProperty@ enabledProp;
bool enabled = true;
void OnLoad(const BML::ModContext &in ctx) {
BML::Config@ config = ctx.BorrowConfig();
if (config is null)
return;
@enabledProp = config.GetProperty("General", "Enabled");
if (enabledProp !is null) {
enabledProp.SetDefaultBoolean(true);
enabledProp.SetComment("Enable the example feature.");
enabled = enabledProp.GetBoolean(true);
}
}
void OnModifyConfig(const BML::ModContext &in ctx,
const BML::ConfigEvent &in event) {
if (event.Category == "General" && event.Key == "Enabled") {
BML::ConfigProperty@ prop = event.BorrowProperty();
if (prop !is null)
enabled = prop.GetBoolean(enabled);
}
}
Use typed getters, setters, and defaults for string, boolean, integer, float,
and key values. OnModifyConfig suppresses recursive edits of the same property.
Input, state, UI commands, and speedrun timing¶
Input is borrowed through ctx.BorrowInputManager() and used within the current
callback:
void OnProcess(const BML::ModContext &in ctx) {
BML::InputHook@ input = ctx.BorrowInputManager();
if (input !is null && input.IsKeyPressed(CKKEY_F5))
BML::UI::AddMessage("F5 pressed by script");
}
ModContext exposes IsInGame, IsInLevel, IsPaused, IsPlaying,
IsCheatEnabled, EnableCheat, ExitGame, ExecuteCommand,
SkipRenderForNextTick, GetSRScore, and GetHSScore.
Loader-owned presentation commands live under BML::UI: messages, mod/map
menus, HUD mode, title, and FPS. Speedrun timing is separate under
BML::Speedrun: visibility, start, pause, reset, and elapsed time. These
commands do not require an active ImGui frame. Drawing controls do; see
Engine and UI.
Guard mutations that require an active level with an explicit state check. Defaults and no-ops prevent a host crash, but an explicit branch produces a clearer script and diagnostic.
Built-in typed snapshots¶
BML::Runtime returns small in-process state, clock, score, and cheat values.
BML::Gameplay reads gameplay data that may be unavailable or use an
unsupported layout, so those calls return a status. Calling a built-in API
outside a valid script callback raises a script exception.
BML::Runtime::State runtime = BML::Runtime::GetState();
BML::Gameplay::LevelState level;
if (runtime.InLevel && BML::Gameplay::ReadLevel(level) == BML::ERROR_OK) {
CKObject@ ball = level.BorrowActiveBall();
}
Catalog and checkpoint reads return complete script-owned arrays. Retain a
snapshot while its source is stable instead of rebuilding it every frame.
Loader events arrive synchronously through OnGameEvent. Copy only the state a
mod needs to retain; there is no script-side event queue to open or poll.
Timers¶
BML+ owns registered timers for the script mod and cancels them at unload. Prefer callback timers for simple delays and intervals:
void SayReady(const BML::ModContext &in ctx,
const BML::TimerEvent &in event) {
ctx.LogInfo("ready");
}
bool Heartbeat(const BML::ModContext &in ctx,
const BML::TimerEvent &in event) {
ctx.LogInfo("heartbeat " + event.CompletedIterations);
return event.CompletedIterations < 5;
}
void OnLoad(const BML::ModContext &in ctx) {
ctx.SetTimeout(1000.0f, SayReady, "ready");
ctx.SetInterval(1000.0f, Heartbeat, "heartbeat");
}
Millisecond variants use SetTimeout and SetInterval; tick variants use
SetTimeoutTicks and SetIntervalTicks. An interval callback returns false
to stop. Use an AngelScript delegate to bind an object method.
Implement BML::Timer when the timer needs object state, start-paused,
repeat-count, or priority configuration. TimerRef supports validity checks,
pause, resume, cancel, state, completed/remaining iterations, and progress.
Commands and completion¶
Use CommandDefinition and delegates for a small command:
void HelloExecute(const BML::ModContext &in ctx,
const BML::CommandEvent &in event) {
ctx.LogInfo("hello " + event.ArgsText);
}
void HelloComplete(const BML::ModContext &in ctx,
const BML::CommandEvent &in event,
BML::CommandCompletion &inout completions) {
completions.Add("world");
}
void OnLoad(const BML::ModContext &in ctx) {
BML::CommandDefinition def;
def.Name = "hello";
def.Alias = "h";
def.Description = "Print hello";
ctx.RegisterCommand(def, HelloExecute, HelloComplete);
}
Implement BML::Command when the command owns state or benefits from a single
object. Name and Execute are required; alias, description, usage, cheat
requirement, enabled state, and completion are optional. Duplicate names or
aliases fail registration. Self-unregister is delayed until the callback
returns.
Next: Engine and UI.