Jump to content

Model Lifecycle Scripts

From OpenBOR
Revision as of 23:49, 21 August 2026 by Dcurrent (talk | contribs)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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.c
  • data/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:

  1. OpenBOR locates the model's existing cache entry.
  2. The model file is read and its template is constructed.
  3. Model properties, animations, ordinary event scripts, and lifecycle commands are parsed.
  4. Temporary parser resources are released.
  5. The model's modelloadscript executes when declared.
  6. Global data/scripts/modelload.c executes when present.
  7. 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:

  1. OpenBOR receives a valid request to unload a currently loaded model or begins a native model-cleanup operation.
  2. The model's modelunloadscript executes when declared.
  3. Global data/scripts/modelunload.c executes when present.
  4. OpenBOR frees the template's animations, palettes, scripts, collections, and other owned resources.
  5. 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 modelloadscript and modelunloadscript handle behavior unique to one template.
  • Global modelload.c and modelunload.c maintain 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() and unload_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.c with exact event-driven notifications.
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.

See Also