Model Lifecycle Scripts
Model lifecycle scripts are event hooks that execute when a model template enters or leaves OpenBOR's loaded model collection. The series provides project-wide global observers and model-owned callbacks for both load and unload transitions.
| Hook | Scope | Execution point |
|---|---|---|
modelloadscript
|
Model-owned | After that model finishes loading |
data/scripts/modelload.c
|
Project-wide | After the loaded model's modelloadscript
|
modelunloadscript
|
Model-owned | Before that model begins native destruction |
data/scripts/modelunload.c
|
Project-wide | After the model's modelunloadscript, before native destruction
|
Model lifecycle is distinct from entity lifecycle. Loading a model makes its template available for spawning one or many entities. Unloading removes that template from active memory. Neither transition represents an entity spawn, death, or removal.
This distinction makes the series useful for model registries, template-specific initialization, dynamic loading systems, model analysis, custom resource maps, diagnostics, subsystem registration, teardown, and other work that should occur once per model transition instead of once per spawned entity or once per loading-screen update.
Global Usage
Create either or both global files in data/scripts:
data/scripts/modelload.cdata/scripts/modelunload.c
OpenBOR loads them automatically. modelload.c observes every completed model load, while modelunload.c observes every model immediately before it is destroyed.
void main()
{
setglobalvar("last_loaded_model_name", modelname);
setglobalvar("last_loaded_model_index", modelindex);
setglobalvar("model_registry_dirty", 1);
}
The example is suitable for modelload.c. Central managers can use the reported name and index to refresh only the affected registry entry rather than scanning the full loaded-model collection during every loading.c execution.
Model Usage
Add either command to a model definition and provide the path to its script file:
modelloadscript data/scripts/models/boss_template_load.c
modelunloadscript data/scripts/models/boss_template_unload.c
The commands belong to the model template that declares them. They do not belong to spawned entities and are not copied into entity modeldata.
void main()
{
setglobalvar("boss_template_ready", 1);
setglobalvar("boss_template_index", modelindex);
}
Model-owned callbacks are useful when one template requires specialized setup or teardown that does not belong in the global observer.
Only one modelloadscript and one modelunloadscript may be assigned to a model. Global and model-owned layers may be used independently or together.
Inline Model Scripts
Both commands support inline script blocks:
modelloadscript @script
void main()
{
setglobalvar("guardian_template_loaded", 1);
}
@end_script
modelunloadscript @script
void main()
{
setglobalvar("guardian_template_loaded", 0);
}
@end_script
Inline form is convenient for concise model-specific bookkeeping. External files are easier to reuse and organize when lifecycle work contains several systems or helper functions.
Event Data
All four lifecycle hooks receive the same automatic local variables:
| Variable | Type | Description |
|---|---|---|
model
|
Object pointer | Pointer to the loaded model template. The pointer is fully available during both load and unload callbacks. |
modelindex
|
Integer | Stable index of the model's cache entry. |
modelname
|
String | Name assigned to the model's cache entry. |
void main()
{
if (model)
{
setglobalvar("lifecycle_model_name", modelname);
setglobalvar("lifecycle_model_index", modelindex);
}
}
The model value refers to a template rather than an entity. Entity property functions and entity-local assumptions do not apply to it. Use the model property API or another model-aware system when inspecting or modifying the template.
modelindex identifies the cache slot and remains stable when an unloadable model is later loaded again. modelname is safe to preserve after an unload callback. The model pointer is not safe to retain after unloading completes.
Model Load Event
The load event executes only after OpenBOR successfully finishes parsing and constructing the model. Defaults, inherited model data, animations, scripts, palettes, collision information, and other declared properties have already been processed when modelloadscript begins.
The load sequence is:
- OpenBOR locates the model's existing cache entry.
- The model file is read and its template is constructed.
- Model properties, animations, ordinary event scripts, and lifecycle commands are parsed.
- Temporary parser resources are released.
- The model's
modelloadscriptexecutes when declared. - Global
data/scripts/modelload.cexecutes when present. - The model enters its normal loaded state and the original load request receives the completed template.
Model-owned logic always executes before the global observer. The global script may therefore consume registration, flags, or other state established by the model's own callback.
Requesting a model that is already loaded does not produce another load event. The event represents an actual transition from cached model definition to loaded template, not each lookup or each entity spawn using that template.
Model Unload Event
The unload event executes while the model remains intact and immediately before OpenBOR begins native destruction.
The unload sequence is:
- OpenBOR receives a valid request to unload a currently loaded model or begins a native model-cleanup operation.
- The model's
modelunloadscriptexecutes when declared. - Global
data/scripts/modelunload.cexecutes when present. - OpenBOR frees the template's animations, palettes, scripts, collections, and other owned resources.
- The template is removed from the loaded model collection while its cache entry remains available for a later reload.
Model-owned teardown always executes before the global observer. Both layers may inspect the model and coordinate cleanup while model is still valid.
The pointer becomes invalid after the unload chain returns and native destruction begins. Cleanup systems that need persistent identification should retain modelname, modelindex, or creator-owned data instead of retaining the pointer.
void main()
{
setglobalvar("last_unloaded_model_name", modelname);
setglobalvar("last_unloaded_model_index", modelindex);
setglobalvar("model_registry_dirty", 1);
}
The example allows a registry manager to remove or rebuild the affected entry without touching the model pointer after the event ends.
Cache and Reload Behavior
OpenBOR distinguishes a cached model definition from a loaded model template. Entries created from data/models.txt retain the model name, source path, flags, and stable cache position even while the full template is unloaded.
Lifecycle scripts follow transitions of the full template:
- Registering the cache entry alone does not execute
modelload. - Loading and completing the template executes the load chain once.
- Looking up or requesting an already loaded template does not repeat the chain.
- Unloading the template executes the unload chain once.
- Loading the same cache entry again parses its model lifecycle commands and executes a new load chain.
- Requesting
unload_model()for an absent model does not load it and therefore produces no lifecycle event.
This behavior provides precise change notifications without requiring a recurring scan to discover whether the loaded collection changed.
Lifecycle Safety
OpenBOR guards lifecycle transitions against recursive self-removal and partially constructed templates. An unload request targeting the same model from its load or unload callback is ignored because that model is already inside a protected lifecycle phase.
The guard keeps one callback from re-entering destruction of its own template. Lifecycle scripts may still coordinate creator-owned cleanup, request later work through flags or registries, and operate on other valid systems during the event.
Model subclasses do not inherit modelloadscript or modelunloadscript from their parent template. Each subclass may declare its own lifecycle callbacks. This prevents parent and child cache entries from sharing compiled callback ownership while preserving normal inheritance for ordinary model data and supported entity event scripts.
Lifecycle Events and Entity Events
| Hook | Object represented | Frequency | Typical responsibility |
|---|---|---|---|
modelloadscript and modelload.c
|
Loaded model template | Once per actual template load | Template registration, analysis, and initialization |
| onmodelcopyscript | Model data being copied for an entity | During applicable model-copy operations | Per-instance model-data adjustment |
| onspawnscript | Newly created entity | Once per applicable entity creation | Entity initialization |
| ondeathscript | Dying entity | During applicable entity death processing | Entity-specific death behavior |
| onkillscript | Entity being removed | Immediately before applicable entity removal | Entity cleanup |
modelunloadscript and modelunload.c
|
Loaded model template | Once per actual template unload | Template deregistration and teardown |
One loaded model may create many entities. Model lifecycle scripts execute for the template transition, while entity events execute separately for individual instances.
Global and Model-Owned Responsibilities
The two scopes support a useful division of responsibility:
- Model-owned
modelloadscriptandmodelunloadscripthandle behavior unique to one template. - Global
modelload.candmodelunload.cmaintain project-wide registries and systems after the model-owned callback has run.
For example, a boss model can publish its specialized combat role during modelloadscript. Global modelload.c can then add the completed template to a project-wide model registry. During removal, the model-owned callback can release its custom relationships before modelunload.c reconciles the common registry.
Other Uses
Quick applications include:
- Model registries - Add and remove exact cache entries without repeatedly scanning every loaded model.
- Template analysis - Inspect capabilities, animations, classifications, resources, or creator-defined metadata once after parsing.
- Subsystem registration - Connect models to combat managers, factories, selectors, randomizers, encounter systems, or development tools.
- Dynamic loading - Track runtime
loadmodel()andunload_model()transitions as they occur. - Model-specific initialization - Prepare template-owned creator state before any entity needs to use it.
- Resource coordination - Build or release creator-managed lookup tables, auxiliary assets, caches, or relationships alongside a template.
- Diagnostics - Log load order, unload order, names, cache indices, reloads, or unexpected lifecycle transitions.
- Loading optimization - Replace repeated full-model scans in
loading.cwith exact event-driven notifications.
Related Scripts
| Script | Relationship |
|---|---|
| loading.c | Recurring loading-screen hook. Model lifecycle events provide exact template transition notifications without repeated collection scans. |
| onmodelcopyscript | Executes during applicable model-data copy operations rather than model-cache loading. |
| onspawnscript | Executes for applicable entity creation after a model template is available. |
| ondeathscript | Model-owned hook for entity death processing. |
| onkillscript | Model-owned hook for entity removal. |