Updateentityscript: Difference between revisions
| Line 89: | Line 89: | ||
== Logical Ticks, Not Display Passes == | == Logical Ticks, Not Display Passes == | ||
Updateentityscript is tied to OpenBOR's logical clock. One screen refresh may process zero, one, or several logical ticks depending on elapsed time, pause state, slow motion, and catch-up limits. | Updateentityscript is tied to OpenBOR's [[Time#Elapsed Time|logical clock]]. One screen refresh may process zero, one, or several logical ticks depending on elapsed time, pause state, slow motion, and catch-up limits. | ||
Consequently: | Consequently: | ||
| Line 169: | Line 169: | ||
=== Updateentityscript and Thinkscript === | === Updateentityscript and Thinkscript === | ||
<code>thinkscript</code> is a separate model command with later and narrower timing. The distinction is: | <code>[[thinkscript]]</code> is a separate model command with later and narrower timing. The distinction is: | ||
{| class="wikitable" | {| class="wikitable" | ||
| Line 269: | Line 269: | ||
OpenBOR compiles the source with its model, then gives each spawned entity its own script context when model scripts are copied into the entity. Instances of the same model share compiled code, while their script-local variable lists remain separate. | OpenBOR compiles the source with its model, then gives each spawned entity its own script context when model scripts are copied into the entity. Instances of the same model share compiled code, while their script-local variable lists remain separate. | ||
Values stored with <code>setlocalvar()</code> can therefore retain state between executions for that entity's Updateentityscript without becoming global to every entity using the model. OpenBOR replaces <code>self</code> before each call and clears that supplied local after execution. | Values stored with <code>[[setlocalvar()]]</code> can therefore retain state between executions for that entity's Updateentityscript without becoming global to every entity using the model. OpenBOR replaces <code>self</code> before each call and clears that supplied local after execution. | ||
Compact persistent-counter example: | Compact persistent-counter example: | ||
| Line 299: | Line 299: | ||
Earlier callbacks may change state that later entities observe during the same logical tick. Later callbacks cannot retroactively change decisions already completed by earlier entities. Spawning and removal may also alter list order over time. | Earlier callbacks may change state that later entities observe during the same logical tick. Later callbacks cannot retroactively change decisions already completed by earlier entities. Spawning and removal may also alter list order over time. | ||
Use explicit timestamps, ownership, unique IDs, entity variables, or project-managed queues when several entities must coordinate deterministically. Systems requiring a complete before-and-after pass across all entities often belong in global [[Update.c]] or [[Updated.c]] logic instead of relying on incidental entity-list order. | Use explicit timestamps, ownership, unique IDs, entity variables, or project-managed queues when several entities must coordinate deterministically. Systems requiring a complete before-and-after pass across all entities often belong in global [[Update|Update.c]] or [[Updated|Updated.c]] logic instead of relying on incidental entity-list order. | ||
== Practical Uses == | == Practical Uses == | ||
| Line 336: | Line 336: | ||
! Typical role | ! Typical role | ||
|- | |- | ||
| [[Update.c]] | | [[Update|Update.c]] | ||
| Global pre-update script called before logical entity processing according to its central update conditions. | | Global pre-update script called before logical entity processing according to its central update conditions. | ||
| Prepare shared systems and state before entity updates begin. | | Prepare shared systems and state before entity updates begin. | ||
| Line 356: | Line 356: | ||
| Add entity-linked display composition without defining logical update cadence. | | Add entity-linked display composition without defining logical update cadence. | ||
|- | |- | ||
| [[Updated.c]] | | [[Updated|Updated.c]] | ||
| Global post-update script called after entity display preparation and before final queue rendering. | | Global post-update script called after entity display preparation and before final queue rendering. | ||
| Finalize shared state and add late display content. | | Finalize shared state and add late display content. | ||
| Line 391: | Line 391: | ||
* [[Updated]] | * [[Updated]] | ||
* [[Onspawnscript]] | * [[Onspawnscript]] | ||
* [[Onblock scripts]] | * [[Move Blocking Events|Onblock scripts]] | ||
* [[Getlocalvar()]] | * [[Getlocalvar()]] | ||
* [[Setlocalvar()]] | * [[Setlocalvar()]] | ||
[[Category:OpenBOR Index]] | |||
[[Category:Script Events]] | |||
[[Category:Model]] | |||
Latest revision as of 23:51, 17 August 2026
Updateentityscript is OpenBOR's per-entity logical update hook. Creators assign it with the model command script.
The two names describe different sides of the same feature:
scriptis the command written in a model definition.updateentityscriptis the engine's internal script name and the descriptive name used for this event.
There is no updateentityscript model command. Compilation messages and engine diagnostics may use that name because OpenBOR initializes the source supplied by script as the model's Updateentityscript.
Updateentityscript runs near the beginning of each eligible logical update for an entity. Its placement before native AI, animation advancement, attack collision, health-display updates, velocity accumulation, gravity, movement, and binding resolution makes it one of the engine's broadest model-level control hooks.
Creators can use it for custom state machines, status processing, movement controllers, dynamic action selection, entity timers, procedural behavior, component coordination, conditional native-AI overrides, or any other logic that must inspect and prepare one entity before the rest of its native update continues.
Syntax
script {path}
# Default
# No corresponding Updateentityscript
{path}- Path to an OpenBOR Script source file.- The command belongs in a model definition.
- OpenBOR loads and compiles the supplied source with the model.
- The source defines a normal
main()entry point. - The script's return value is ignored.
Example model header:
name example_character
type enemy
script data/scripts/example_update.c
Event code may also be embedded directly in the model:
script @script
void main()
{
void self = getlocalvar("self");
update_custom_state(self);
}
@end_script
Local Variables
| Local | Type | Value |
|---|---|---|
self
|
Entity pointer | Entity whose logical update is in progress. |
No opponent, owner, animation index, logical time, input state, movement amount, or cancellation value is supplied automatically. Retrieve additional state from self, system properties, player properties, entity variables, global variables, or project-managed data.
Runtime Timing
For each logical engine tick, the relevant order is:
- OpenBOR updates level scrolling and applicable status systems.
- The entity update pass visits existing entities in active entity-list order.
- Entities spawned during the current logical tick are excluded from the main update.
- Level loss and lifespan checks run for the current entity when a level is active.
- Processing stops when those checks remove the entity.
- OpenBOR tests whether the entity is currently frozen.
- Frozen entities receive timer expansion instead of Updateentityscript and the remaining main update stages.
- For an unfrozen entity, OpenBOR assigns
selfand executes Updateentityscript. - Processing stops when the script removed the entity.
- Native action and AI processing runs when the entity's think timer is due.
- The current animation may advance.
- Attack collision is evaluated.
- Displayed health and magic values update.
- Current X and Z velocity is accumulated into the entity's pending lateral movement.
- Once the main pass has visited every entity, OpenBOR resolves gravity and pending movement for all existing entities.
- Binding positions and states are resolved after movement.
- Removed entities are compacted out of the active list and pending lateral movement values are reset.
This callback is therefore an early-update hook, not a complete replacement for the entity update. Native processing continues after main() returns unless the script changes the state that controls later stages or removes self from play.
Logical Ticks, Not Display Passes
Updateentityscript is tied to OpenBOR's logical clock. One screen refresh may process zero, one, or several logical ticks depending on elapsed time, pause state, slow motion, and catch-up limits.
Consequently:
- No logical tick means no Updateentityscript call.
- One logical tick permits one call for each eligible entity.
- Several catch-up ticks may execute the same entity's Updateentityscript several times before the next screen refresh.
- Display refresh rate does not define the callback's update rate.
Use logical timestamps for timed gameplay behavior. Do not increment a timer under the assumption that one call represents one displayed refresh.
Execution Conditions
| Condition | Executes? | Notes |
|---|---|---|
| Existing, unfrozen entity during a logical tick | Yes | The entity needs an initialized Updateentityscript. |
| Entity was spawned during the current logical tick | No | Its first main update is deferred until a later logical tick. That tick may still occur before the next display pass when the engine is catching up. |
| Entity is frozen by an active freeze condition | No | OpenBOR expands relevant timestamps and skips the main update stages. |
| Native AI think timer is not due | Yes | nextthink governs later action, AI, and Thinkscript processing - it does not gate Updateentityscript.
|
| Entity is outside the visible viewport | Yes | Visibility does not gate logical updates, though level loss or removal rules may remove the entity before the callback. |
| Game is paused | No | The central logical entity-update loop does not advance while pause is active. |
| Existing entity on an active non-level engine screen | Yes | Screens that use the normal entity-update path can execute the hook without an active level. |
Use Onspawnscript for initialization that must occur immediately when the entity enters play. Updateentityscript takes over continuous logic beginning with the entity's first eligible logical tick.
Applying a freeze condition from inside Updateentityscript does not cause OpenBOR to restart the current update through its frozen branch. The freeze test already occurred. Later native stages still belong to the current pass unless the script also changes their controlling state or removes the entity. The new freeze condition governs later logical ticks.
Relationship to Native AI
Updateentityscript executes before check_ai(). This gives creators an opportunity to prepare or override the state that native action and AI code will inspect.
Useful patterns include:
- Selecting a custom action before native AI chooses one.
- Delaying or advancing the next native think time.
- Supplying a target, destination, or movement intent.
- Enabling or disabling native AI control according to project state.
- Changing animation, direction, aggression, range, or other decision inputs.
- Removing an expired helper or effect entity before native logic runs.
Compact state-controller example:
void main()
{
void self = getlocalvar("self");
update_status_effects(self);
select_custom_action(self);
}
Project helper update_status_effects() can manage timers and state flags, while select_custom_action() can prepare the entity before native action and AI processing begins.
Updateentityscript and Thinkscript
thinkscript is a separate model command with later and narrower timing. The distinction is:
| Feature | script - Updateentityscript
|
thinkscript
|
|---|---|---|
| Frequency | Every eligible, unfrozen logical tick. | Only when nextthink is due and end-game state does not suppress thinking.
|
| Position | Before native action and AI processing. | After the due native action and AI routines. |
| Primary role | Continuous per-entity state preparation and logical control. | Logic synchronized with the entity's scheduled thinking cadence. |
Effect of noaicontrol
|
Does not suppress the callback. | Native think() is skipped, while Thinkscript itself still executes when the think block is due.
|
Use Updateentityscript when behavior must run regardless of whether the native think timer is ready. Use Thinkscript when the project's work naturally belongs to the scheduled AI-thinking event.
Animation and Attack Timing
The current animation state is available when Updateentityscript begins. Native animation advancement has not yet occurred for the logical tick.
Script may change the animation or indexed animation position directly. Later animation processing observes the resulting state and may advance it when its timing is due. Entering an indexed animation position can also invoke Animationscript immediately through the normal animation setter or frame-update path.
Attack collision runs after animation processing. Changes to attack state, animation, collision data, position, target, or related entity properties can therefore influence collision evaluation later in the same logical tick.
Compact conditional-animation example:
void main()
{
void self = getlocalvar("self");
if(should_enter_defensive_action(self))
{
enter_defensive_action(self);
}
}
Project helpers can inspect input, health, nearby threats, attack history, or custom variables before selecting the action. Native animation and attack processing then continues from the chosen state.
Movement Timing
Updateentityscript runs before OpenBOR adds current X and Z velocity to pending lateral movement. Velocity and speed-multiplier changes made by the script therefore affect that accumulation during the same logical tick.
Script may also add to pending X or Z movement through entity properties or project helpers. Native velocity is added afterward, then the combined movement reaches gravity and movement resolution after the main pass has visited every entity.
Direct position changes behave differently from pending movement. Changing world position immediately relocates the entity before native AI, animation, and attack collision run. Pending movement is resolved later through OpenBOR's movement and obstruction systems.
Compact movement-controller example:
void main()
{
void self = getlocalvar("self");
apply_surface_drag(self);
apply_magnetic_pull(self);
}
These helpers might adjust velocity for the current tick, contribute pending movement, or select direct repositioning according to the intended collision behavior.
Use pending movement or velocity when native walls, platforms, obstacles, screen limits, and Onblock hooks should participate. Use direct position changes only when intentional relocation is preferable to ordinary movement resolution.
Removing the Entity
OpenBOR checks self's existence immediately after Updateentityscript returns. Removing the entity stops its remaining main-update stages for the current logical tick.
This provides a clean pattern for temporary controllers, procedural effects, summoned helpers, or other objects with script-defined expiration:
void main()
{
void self = getlocalvar("self");
if(custom_lifetime_complete(self))
{
remove_custom_entity(self);
}
}
Project helper remove_custom_entity() should use the removal path appropriate to the object. Once removal clears the entity's existence state, native AI, animation advancement, attack collision, health-display updates, and velocity accumulation are skipped for that entity.
Returning an integer or other value does not cancel native processing. The return value is ignored.
Per-Entity Script State
OpenBOR compiles the source with its model, then gives each spawned entity its own script context when model scripts are copied into the entity. Instances of the same model share compiled code, while their script-local variable lists remain separate.
Values stored with setlocalvar() can therefore retain state between executions for that entity's Updateentityscript without becoming global to every entity using the model. OpenBOR replaces self before each call and clears that supplied local after execution.
Compact persistent-counter example:
void main()
{
void self = getlocalvar("self");
int update_count = getlocalvar("update_count");
if(isempty(update_count))
{
update_count = 0;
}
update_count++;
setlocalvar("update_count", update_count);
use_update_count(self, update_count);
}
Entity variables remain preferable when several scripts attached to the same entity need the value. Global variables or indexed variables suit intentionally shared state. Script-local values suit private state belonging only to the Updateentityscript context.
Entity Ordering and Cross-Entity Logic
Updateentityscript callbacks execute in active entity-list order. This order reflects internal entity management rather than visual depth, player number, model declaration order, or guaranteed spawn priority.
Earlier callbacks may change state that later entities observe during the same logical tick. Later callbacks cannot retroactively change decisions already completed by earlier entities. Spawning and removal may also alter list order over time.
Use explicit timestamps, ownership, unique IDs, entity variables, or project-managed queues when several entities must coordinate deterministically. Systems requiring a complete before-and-after pass across all entities often belong in global Update.c or Updated.c logic instead of relying on incidental entity-list order.
Practical Uses
| Pattern | Use of Updateentityscript |
|---|---|
| Custom state machine | Evaluate project-defined states every logical tick and prepare the entity's action before native AI. |
| Status controller | Process poison, regeneration, armor, cooldown, charge, transformation, or other entity-specific systems. |
| Movement controller | Modify velocity, pending movement, direction, acceleration, drag, orbit, homing, or scripted path behavior before movement resolution. |
| Dynamic action selection | Choose attacks, evasions, counters, assists, cancels, or contextual animations from live game state. |
| Component coordinator | Keep owner, child, weapon, helper, platform, or bound entities synchronized through explicit project state. |
| Temporary entity lifetime | Remove effects, hazards, controllers, markers, or helpers when project-defined conditions expire. |
| Native behavior preparation | Change targets, timers, AI inputs, collision state, animation state, or movement values before their native consumers run. |
Comparison with Other Update Hooks
| Hook | Scope and timing | Typical role |
|---|---|---|
| Update.c | Global pre-update script called before logical entity processing according to its central update conditions. | Prepare shared systems and state before entity updates begin. |
script - Updateentityscript
|
Per-entity hook called early in every eligible, unfrozen logical tick. | Prepare and control one entity before its native AI, animation, attack, and movement work. |
| Thinkscript | Per-entity callback called inside scheduled think processing after native action and AI routines. | Extend logic that follows the entity's think cadence. |
| Animationscript | Per-entity animation hook called when an indexed animation position is entered or updated. | Implement animation-position actions, branches, and transitions. |
| Ondrawscript | Per-entity display callback called after native entity visuals enter the sprite queue. | Add entity-linked display composition without defining logical update cadence. |
| Updated.c | Global post-update script called after entity display preparation and before final queue rendering. | Finalize shared state and add late display content. |
Performance Considerations
Every qualifying entity can execute Updateentityscript on every logical tick, including several times during one catch-up refresh. Work inside the callback therefore scales with both entity count and logical update count.
Keep routine work bounded where practical. Repeated full-entity scans, file access, resource loading, large temporary arrays, or duplicated path calculations can become expensive when every entity performs them independently.
Shared calculations may be performed once in a global update hook and consumed by each entity. Stable resources and lookup results can be cached. Entity-local conditions can prevent unnecessary helper calls when a feature is inactive.
Common Mistakes
- Writing
updateentityscriptin the model instead of the actualscriptcommand. - Treating one callback as one displayed refresh instead of one logical entity update.
- Expecting the hook to execute during active freeze or pause conditions.
- Expecting a newly spawned entity to receive its main update during the spawn tick.
- Assuming
nextthinkcontrols Updateentityscript frequency. - Returning a value and expecting it to cancel native processing.
- Changing velocity without accounting for later native velocity accumulation and movement resolution.
- Teleporting through direct position changes when ordinary pending movement and collision handling were intended.
- Applying a freeze inside the callback and expecting the already-started update to switch to the frozen branch.
- Depending on active entity-list order as a permanent cross-entity priority.
- Performing costly global searches independently from every entity on every logical tick.