Ondrawscript
The ondrawscript command defines a model-level display hook for each entity using that model. OpenBOR executes the script during an eligible display pass after it has queued the native visuals for every active entity, but before the sprite queue is rendered to the screen.
This timing makes Ondrawscript a powerful display-composition tool. Creators can add entity-anchored graphics, status indicators, procedural effects, custom attachments, trails, targeting markers, replacement visuals, shadows, reflections, and diagnostic overlays without replacing the engine's logical entity update.
Despite its name, Ondrawscript is not limited to entities whose native sprite is currently visible. OpenBOR schedules the hook for every existing entity with an initialized Ondrawscript before testing blink visibility or the current sprite index. Off-screen entities and entities without a queueable current sprite may therefore execute it as well.
Syntax
ondrawscript {path}
# Default
# No corresponding Ondrawscript
{path}- Path to an OpenBOR Script source file.- The command belongs in a model definition.
- OpenBOR loads and compiles the supplied script 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
ondrawscript data/scripts/example_draw.c
Event code may also be embedded directly in the model:
ondrawscript @script
void main()
{
void self = getlocalvar("self");
draw_entity_effects(self);
}
@end_script
Local Variables
| Local | Type | Value |
|---|---|---|
self
|
Entity pointer | Entity whose Ondrawscript is executing. |
No coordinates, animation index, drawing layer, sprite handle, draw method, screen handle, or visibility result is supplied automatically. Retrieve any needed state from self, system properties, entity variables, or project-managed data.
Runtime Timing
During a normal gameplay refresh, the relevant order is:
- OpenBOR runs the global and level pre-update scripts and any required logical update cycles.
- The screen is cleared, then the level background, status display, and text objects are prepared.
- OpenBOR visits every existing entity in the active entity list.
- Each entity with an initialized Ondrawscript is placed on a deferred callback stack.
- Native entity visuals are added to the sprite queue, including applicable sprites, mirror images, shadows, status displays, and player indicators.
- Once every entity has been visited, OpenBOR executes the deferred Ondrawscript callbacks in active entity-list order.
- Global and level post-update scripts execute.
- OpenBOR sorts and renders the completed sprite queue.
- The finished screen is presented and the queue is cleared.
The deliberate second pass is the feature's defining behavior. Every native entity display item is already available in the queue before the first Ondrawscript begins, while script-added queue items still arrive in time for the current refresh.
Logical updates and display passes are not one-to-one. One display pass may follow zero, one, or several logical update cycles. Ondrawscript executes once for each eligible entity during the display pass, not once for every logical cycle processed beforehand.
Execution Conditions
| Condition | Executes? | Notes |
|---|---|---|
| Existing entity during an eligible display pass | Yes | The entity needs an initialized Ondrawscript. |
| Entity is frozen or logical updates did not advance it | Yes | Display scheduling is independent of whether the entity performed a logical update. |
| Entity is blinking and its native sprite is omitted for this refresh | Yes | Callback scheduling occurs before the blink test. |
| Current sprite index cannot be queued | Yes | Callback scheduling occurs before native sprite validation. |
| Entity is outside the visible viewport | Yes | No viewport test precedes callback scheduling. |
| Game is paused | No | The central entity display pass is skipped while pause is active. |
| Entity is spawned by another Ondrawscript | Not until the next eligible display pass | The deferred callback stack is complete before any Ondrawscript executes. |
The same entity display path may also operate on active engine screens outside a level, such as selection, menu, scene, and other screens that use entities. Ondrawscript is consequently a display-pass hook rather than a level-only hook.
Sprite Queue Behavior
Most script drawing functions add work to OpenBOR's central sprite queue. Common examples include drawsprite(), drawscreen(), drawstring(), drawbox(), drawline(), and drawdot().
OpenBOR renders the queue only after every Ondrawscript and the post-update scripts have finished. Script-added visuals therefore appear during the same refresh.
Queue insertion time does not by itself determine which visual appears on top. OpenBOR sorts items by their drawing Z value and then by sortid. Items with identical sort values should not depend on insertion order as a stable tie-breaker.
drawsprite() accepts an optional sortid, allowing precise placement among items sharing a drawing Z value. drawscreen() and primitive queue functions such as drawbox(), drawline(), and drawdot() use a sort ID of 0, so their Z value is the primary creator-controlled layer.
Text from drawstring() is queued as font sprites at the font layer plus the supplied layer offset. Choose that offset according to the intended relationship with entity and interface graphics.
Direct-to-Screen Functions
Functions whose names end in toscreen, such as drawspritetoscreen() and drawboxtoscreen(), write to a supplied screen handle immediately instead of adding an item to the central queue. Their composition behavior differs from the normal queued functions and depends on the destination screen and when that screen is later used.
Queued drawing is normally the clearest choice for visuals intended to participate in OpenBOR's standard layer sorting.
Advanced Queue Control
The deferred timing also permits advanced manipulation of the completed queue:
drawspriteq()renders selected queue contents to a screen before the engine's final queue render.clearspriteq()clears the shared queue rather than only the calling entity's entries.
Both functions affect global display state. Calling clearspriteq() from one entity's Ondrawscript may remove queued backgrounds, native graphics for every entity, interface elements, and drawings added by earlier callbacks. Calling drawspriteq() without a deliberate compositing plan may cause queue contents to be rendered again during the normal final pass.
Reserve direct queue management for systems intentionally replacing or capturing part of OpenBOR's display composition.
Native Visual State Is Already Queued
OpenBOR prepares an entity's native sprite and related display items before any Ondrawscript executes. Changes made to native visual state during the callback are therefore generally too late to alter items already queued for the current refresh.
Examples include changing:
- Position or facing.
- Current animation or animation index.
- Color map or alpha.
- Draw method properties.
- Native shadow configuration.
- Native sprite selection.
- Layer or sort ID used by the already queued sprite.
Such changes can affect later refreshes and may influence script-added drawing immediately, but they do not retroactively rebuild the native queue entry. Use an earlier logical hook when the native sprite itself must be changed for the current display pass. Use drawing functions inside Ondrawscript when the desired graphic should be added immediately.
This distinction supports deliberate overlay and replacement techniques. Projects may suppress native rendering through earlier state or model configuration, then use Ondrawscript to submit a custom visual. Merely changing the entity to an invisible state inside Ondrawscript does not remove the native sprite that was already queued.
Coordinates
Queue drawing functions use screen coordinates, while entity position properties use OpenBOR's world axes:
- X - Horizontal world position.
- Z - World depth and the basis of normal entity drawing order.
- Y - Vertical altitude. Legacy script interfaces may call this axis A.
Native entity display converts world position to screen position by subtracting the current camera position, combining depth and altitude for screen Y, and applying applicable screen-shake offsets. Entities configured to ignore their own quake offset use a slightly different conversion.
Ondrawscript does not receive the native conversion result. Projects that attach several custom elements to entities benefit from a shared helper that performs the same world-to-screen conversion and respects vertical scrolling and quake configuration.
Compact entity-anchored marker example:
void main()
{
void self = getlocalvar("self");
int screen_x = entity_screen_x(self);
int screen_y = entity_screen_y(self);
int layer = entity_display_layer(self) + 1;
draw_status_marker(screen_x, screen_y - 48, layer);
}
The helper names in this example represent project functions. Keeping coordinate conversion and layer selection in shared helpers avoids repeating camera, shake, set-layer, platform, and other display-order rules throughout model scripts.
Practical Uses
| Pattern | Use of ondrawscript
|
|---|---|
| Status visualization | Draw poison, armor, stun, charge, targeting, team, objective, or interaction indicators anchored to the entity. |
| Custom attachments | Draw weapons, equipment, aura components, modular body pieces, or costume layers selected from live entity state. |
| Procedural effects | Add glows, trails, afterimages, motion lines, shadows, reflections, or generated shapes without adding each result to the native animation data. |
| Replacement rendering | Submit a project-controlled visual for entities whose native display was suppressed through an earlier hook or model configuration. |
| Entity-linked interface | Position names, gauges, prompts, damage previews, or contextual commands over world objects. |
| Diagnostics | Visualize collision boxes, range checks, origins, paths, targets, ownership, binding offsets, movement vectors, or AI state. |
Conditional Status Graphic
void main()
{
void self = getlocalvar("self");
if(getentityvar(self, "poisoned"))
{
draw_poison_indicator(self);
}
}
Project helper draw_poison_indicator() can select the graphic, convert the entity position, and choose a layer above the native sprite. The callback itself remains focused on deciding when the indicator belongs in the display.
Layered Attachment and Trail
void main()
{
void self = getlocalvar("self");
draw_entity_trail(self);
draw_equipment_layers(self);
}
Separate helpers can place trail elements behind the entity and equipment elements above it by submitting different Z and sortid values. Callback order alone should not be used to establish those relationships.
Collision Diagnostics
void main()
{
void self = getlocalvar("self");
if(getglobalvar("show_collision_debug"))
{
draw_entity_collision_debug(self);
}
}
This pattern allows a project-wide switch to reveal collision boxes, attack boxes, entity origins, or other runtime geometry for every model that includes the hook.
Callback Ordering and Shared State
Deferred callbacks execute in active entity-list order. This order generally reflects internal entity allocation, not visual depth, player priority, or model declaration order. Spawning and removal can also change it over time.
Use queue Z and sortid for visual ordering. Use explicit project state when one entity's callback must exchange information with another. Logic whose result affects gameplay should normally live in an update or event hook rather than depending on display-callback order.
Since every qualifying entity may execute code on every display pass, costly full-entity searches, repeated resource setup, file access, or unnecessary array construction can scale quickly. Cache stable data and keep routine display work bounded where practical.
Comparison with Other Update Hooks
| Hook | Scope and timing | Typical role |
|---|---|---|
| Update.c | Global pre-update script, called before logical entity processing when its update conditions are met. | Prepare global state, input-derived systems, and logic needed before native updates. |
| Animationscript | Model animation-index update hook, called when an entity enters or is moved to an indexed animation position. | Implement animation actions, branches, state transitions, and indexed-position behavior. |
ondrawscript
|
Per-entity display callback, deferred until native visuals for all entities have entered the queue. | Add or manage entity-linked display composition for the current refresh. |
| Updated.c | Global post-update script, called after the entity display pass and before final queue rendering. | Finalize global state and add late display content after entity callbacks. |
Ondrawscript should not be used as a substitute for deterministic gameplay updates. Display refresh frequency can differ from logical update frequency, the hook does not run while paused, and entities outside the viewport still qualify. Gameplay timers, movement, damage, AI, and collision decisions belong in logical or event-driven hooks unless they are intentionally coupled to display passes.
Common Mistakes
- Assuming the callback runs only when
self's sprite is visible. - Treating one callback as one logical game update.
- Moving or hiding
selfand expecting its already queued native sprite to change immediately. - Supplying world coordinates directly to screen-space drawing functions.
- Ignoring camera, vertical-scroll, altitude, or quake offsets when anchoring graphics.
- Assuming later drawing calls always appear above earlier queue entries.
- Depending on entity-list callback order for visual layering or gameplay logic.
- Clearing the shared sprite queue when only one entity's custom graphic should be removed.
- Performing expensive global searches independently from every entity on every display pass.
See Also
- Animationscript
- Update
- Updated
- Drawsprite()
- Drawscreen()
- Drawstring()
- Drawbox()
- Drawline()
- Drawdot()
- Drawspriteq()
- Clearspriteq()
- Getentityproperty