Jump to content

Update

From OpenBOR

update.c is OpenBOR's project-wide pre-update hook. The engine loads it from a fixed path and executes it near the beginning of eligible gameplay refresh passes, after player input has been refreshed but before key scripts, logical time-step processing, and entity updates.

This placement makes update.c a powerful coordination point for systems that must prepare shared state before gameplay objects act. Typical uses include input orchestration, global state machines, encounter directors, controller arbitration, command routing, event-collection setup, target registries, shared timers, cached queries, and other project managers.

update.c is global in scope. It does not belong to a model or level, remains available across levels, and executes only once for each eligible refresh pass. It is not a per-entity or per-logical-tick callback.

File and Syntax

OpenBOR looks for the following file automatically:

data/scripts/update.c

No model, level, or configuration command attaches the file. Projects that do not need the hook may omit it.

The file is loaded and compiled during engine startup, then cleared when the engine shuts down. Source uses a normal main() entry point:

void main()
{
    global_system_begin();
}

global_system_begin() represents a project-defined helper in this compact example. Logic may instead be written directly inside main().

The return value of main() is ignored. Changes made through script functions, variables, entities, or engine properties provide the hook's effects.

Execution Timing

The relevant order for an ordinary unpaused gameplay refresh is:

  1. OpenBOR calculates the real-time interval and refreshes player input.
  2. Global update.c executes.
  3. Current level updatescript executes, when one exists.
  4. Player, level, entity, and global key scripts process applicable input.
  5. OpenBOR calculates how many logical time steps are due.
  6. Scroller, timer, status, and entity processing run once for each due logical step.
  7. Model update scripts run from inside each eligible entity's native update during those steps.
  8. OpenBOR prepares screen content and entity sprites.
  9. Global Updated.c executes, followed by the current level's post-update script.
  10. Queued screen content is rendered.

Newly refreshed input is therefore available to update.c. Shared state changed by the script is visible to the key scripts and entity logic that follow during the same refresh pass.

The global hook executes before its level counterpart. Projects may prepare common state in update.c, then let the active level extend or specialize that state in its own pre-update script.

Refresh Passes and Logical Time

OpenBOR's refresh layer and logical time-step layer are distinct. One call to the central update routine may process zero, one, or several logical steps depending on accumulated real time and catch-up requirements.

update.c executes once before that time-step loop. Consequently:

  • Some refresh passes may run update.c without updating any entity when insufficient logical time has accumulated.
  • Delayed refreshes may run update.c once and then update every eligible entity several times.
  • Model update scripts may execute many times after one call to update.c.
  • openborvariant("elapsed_time") may remain unchanged between consecutive calls or advance several counts after one call returns.

This frequency is ideal for pass-level preparation and orchestration. Exact logical-tick simulation should compare elapsed time deliberately or use a hook inside logical entity processing.

Practical Uses

Pattern Use of update.c
Global input orchestration Sample newly refreshed input, combine player intent, update shared command buffers, prepare tag requests, or switch project control modes before key and entity scripts act.
Two-phase manager setup Clear event collections, open transactions, reset aggregate counters, or initialize registries that model and event scripts will populate during entity processing.
Encounter direction Prepare wave, objective, threat, spawn, or pacing decisions shared by entities from many different models.
Shared state machines Advance project-wide modes such as combat rules, cooperative systems, tutorials, cut-ins, accessibility behavior, or scripted overlays.
Cached queries Build or refresh information that several later scripts need, avoiding repeated full-entity scans from each model.
Global timing gates Check elapsed logical time and open scheduled actions before the current pass processes its entities.
Diagnostics Begin pass measurements, clear trace buffers, record input state, or expose pre-update values to development tools.

Input Orchestration

Input is current when update.c begins. Projects can normalize global commands before ordinary key scripts consume the pass:

void main()
{
    sample_cooperative_commands();
    update_control_mode_requests();
}

Per-player edge-triggered actions still belong in the appropriate key-script hook. update.c is most useful when several players, entities, or systems need the same interpreted input state.

Beginning a Shared Collection Phase

Global managers can clear or initialize their working data before entity logic contributes results:

void main()
{
    combat_events_begin_pass();
    target_registry_begin_pass();
    threat_map_begin_pass();
}

Model updates, animation scripts, and combat events may then add information during logical processing. Updated.c can resolve or commit the finished collection near the end of the refresh pass.

Scheduled Project Logic

Logical time should be tested explicitly when a global system requires tick-based scheduling:

void main()
{
    int now = openborvariant("elapsed_time");
    void next_update = getglobalvar("director_next_update");

    if(next_update == NULL() || now >= next_update)
    {
        update_encounter_director();
        setglobalvar("director_next_update", now + 100);
    }
}

This pattern avoids assuming that main() itself represents one logical clock tick.

Local Variables and Context

OpenBOR supplies no event-specific local variables to update.c. The script receives no automatic self, player index, entity handle, attacker, or level handle.

Context may be obtained through system and property functions such as openborvariant(), player accessors, entity enumeration, global variables, indexed variables, and project-maintained handles. Useful system values include elapsed_time, game_paused, in_level, current_set, and current_stage.

Variables declared inside main() follow normal function scope. Values intentionally stored with setlocalvar() belong to the update.c script object and persist for later executions of this same script.

Local storage is not shared with updated.c. Use global variables, indexed variables, entity variables, or another shared engine object when both hooks need the same data.

Pause Behavior

Default update.c execution requires active, unpaused gameplay. Pausing stops the hook along with logical game processing.

Situation update.c
Unpaused gameplay Executes once for the eligible refresh pass.
Paused gameplay Does not execute by default.
Non-game screen Does not execute by default.
Any central update call with alwaysupdate 1 Executes.

The alwaysupdate setting belongs in data/script.txt:

alwaysupdate 1

# Default
alwaysupdate 0

Enabling alwaysupdate makes update.c execute whenever OpenBOR calls its central update routine, including pauses and non-game screens. This setting also expands Updated.c execution.

Level-only properties and entity assumptions need guards under alwaysupdate 1. Test openborvariant("in_level") and relevant screen-status variants before accessing context that may not exist.

Logical game time still stops during a pause. Running update.c through alwaysupdate does not cause native entity updates, animation timing, movement, or the level clock to advance.

Relationship to Other Update Hooks

Hook Scope Placement
data/scripts/update.c Complete project Once before key scripts and all due logical entity updates in an eligible refresh pass.
Level updatescript Current level Immediately after global update.c, while that level exists.
Model update script Eligible entity using that model Inside each native entity update during every due logical step.
data/scripts/updated.c Complete project Near the end of the refresh pass after logical processing and screen preparation.

Choose update.c for work shared across the project that must be ready before gameplay objects process the pass. Level-specific rules belong to the level pre-update script; per-entity behavior belongs to the model update script.

Common Mistakes

Mistake Result Better approach
Assuming one call per logical tick Timers or simulations drift when a refresh contains zero or several time steps. Track elapsed_time or use logical entity processing.
Expecting self No entity is supplied to the global script. Obtain explicit handles through player, entity, global, or system interfaces.
Sharing state with setlocalvar() across global pre-update and post-update files Each file reads a separate local-variable table. Use global, indexed, or entity variables for shared state.
Expecting default pause execution update.c stops while gameplay is paused. Enable alwaysupdate only when broader execution is intended.
Accessing level data under alwaysupdate 1 without a guard The hook can execute when no level exists. Test in_level first.
Scanning every entity for several unrelated systems Work multiplies rapidly in large projects. Maintain shared registries or combine queries into one preparation pass.
Placing stage-only rules in the global file Logic remains active across the whole project and requires repeated stage checks. Use the level's pre-update script.

See Also