<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
	<id>https://chronocrash.com/openbor/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dcurrent</id>
	<title>OpenBOR - User contributions [en]</title>
	<link rel="self" type="application/atom+xml" href="https://chronocrash.com/openbor/wiki/api.php?action=feedcontributions&amp;feedformat=atom&amp;user=Dcurrent"/>
	<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php/Special:Contributions/Dcurrent"/>
	<updated>2026-10-21T22:28:11Z</updated>
	<subtitle>User contributions</subtitle>
	<generator>MediaWiki 1.46.2</generator>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Category:Pages_with_syntax_highlighting_errors&amp;diff=1107</id>
		<title>Category:Pages with syntax highlighting errors</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Category:Pages_with_syntax_highlighting_errors&amp;diff=1107"/>
		<updated>2026-10-14T14:39:31Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Created page with &amp;quot;An annoying aspect of Media-wiki is the delicacy of syntax highlighting - as in properly display code samples. Often the system will work perfectly for a time, then spontaneously fail as the host server makes unannounced updates. Please contact me ([https://www.chronocrash.com/forum/members/dcurrent.2/ DCurrent]) at the [https://www.chronocrash.com/forum ChronoCrash forum] if you see pages appearing here.&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;An annoying aspect of Media-wiki is the delicacy of syntax highlighting - as in properly display code samples. Often the system will work perfectly for a time, then spontaneously fail as the host server makes unannounced updates. Please contact me ([https://www.chronocrash.com/forum/members/dcurrent.2/ DCurrent]) at the [https://www.chronocrash.com/forum ChronoCrash forum] if you see pages appearing here.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1106</id>
		<title>Animation Overview</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1106"/>
		<updated>2026-09-27T13:23:29Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Animation identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Animations are one of OpenBOR&#039;s fundamental building blocks. Models use animations to describe how they look and behave over time. Walking, standing, attacking, falling, taking damage, and many other actions are represented by ordered animation frames.&lt;br /&gt;
&lt;br /&gt;
Each animation belongs to a model and occupies a recognized animation slot such as &amp;lt;code&amp;gt;idle&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;walk&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;attack1&amp;lt;/code&amp;gt;. OpenBOR&#039;s native logic selects many of these slots automatically. Scripts may also select an animation or frame directly.&lt;br /&gt;
&lt;br /&gt;
An animation frame is more than an image. It may also contain timing, offsets, collision boxes, movement, sounds, draw settings, entity spawns, and other behavior.__TOC__&lt;br /&gt;
&lt;br /&gt;
== Basic structure ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; command begins an animation definition. Commands following it configure the animation or prepare properties for its frames. Each &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command adds one frame using the properties currently in effect.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 12&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example creates a looping idle animation with three frames. With the default centisecond delay unit, each frame lasts 0.12 seconds.&lt;br /&gt;
&lt;br /&gt;
Indentation and blank lines are optional, but they make animation blocks easier to read.&lt;br /&gt;
&lt;br /&gt;
=== Animation identifiers ===&lt;br /&gt;
&lt;br /&gt;
The value following &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; identifies the animation&#039;s purpose. Common examples include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Identifier&lt;br /&gt;
! Typical purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;idle#&amp;lt;/code&amp;gt;&lt;br /&gt;
| Standing without moving.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;walk#&amp;lt;/code&amp;gt;&lt;br /&gt;
| Ordinary movement.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;&lt;br /&gt;
| Initial or rising jump state.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;land&amp;lt;/code&amp;gt;&lt;br /&gt;
| Landing from a jump or fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reacting to damage.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fall#&amp;lt;/code&amp;gt;&lt;br /&gt;
| Knockdown or falling reaction.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rise#&amp;lt;/code&amp;gt;&lt;br /&gt;
| Recovering from a fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;attack#&amp;lt;/code&amp;gt;&lt;br /&gt;
| Native attack animation.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;freespecial#&amp;lt;/code&amp;gt;&lt;br /&gt;
| Creator-configured special or command move.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;follow#&amp;lt;/code&amp;gt;&lt;br /&gt;
|General use animations inaccessible to native engine logic and only activated by creator defined follow triggers or scripts.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Animation identifiers are behavioral slots, not arbitrary labels. Supplying an animation makes the corresponding behavior available to native engine logic and scripts. Some model types require certain animations to operate normally, while others are optional.&lt;br /&gt;
&lt;br /&gt;
== Frame construction ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR reads model files from top to bottom. Frame property commands update temporary state, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; commits a copy of that state to the animation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    delay 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 25&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame receives a delay of 10. The second receives a delay of 25 because the delay changes before its &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
Commands placed after a &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line cannot alter the frame that was already added. Most frame properties remain active for subsequent frames until another command changes or disables them. Some one-shot properties, such as a frame sound, reset after the frame is added.&lt;br /&gt;
&lt;br /&gt;
Define important state explicitly in each animation. In particular, specifying a delay inside every animation avoids accidentally inheriting a previously active delay while the model file is being parsed.&lt;br /&gt;
=== Frame numbering ===&lt;br /&gt;
Frames use zero-based indexes. The first frame line is frame 0, the second is frame 1, and so on. Commands that target a frame number use these indexes unless their own documentation states otherwise.&lt;br /&gt;
&lt;br /&gt;
The same image may appear more than once when different timing or behavior is needed. OpenBOR reuses its globally cached pixel data, so repeating an image does not require another pixel-data allocation.&lt;br /&gt;
&lt;br /&gt;
Frame &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt; adds a logical frame without a visible sprite, which can be useful for invisible states or script-controlled behavior.&lt;br /&gt;
&lt;br /&gt;
== Offsets ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt; command selects the anchor point inside a frame image.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
offset 32 96&lt;br /&gt;
frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR places the selected point at the entity&#039;s world position. For a standing character, the anchor is usually near the ground between the character&#039;s feet.&lt;br /&gt;
&lt;br /&gt;
Choose a consistent animation canvas size during production and align every source frame to the same anchor before export. Guides for the ground line and horizontal center make this much faster than correcting alignment one frame at a time in the model file.&lt;br /&gt;
&lt;br /&gt;
OpenBOR automatically trims unused image space when it loads sprites. Consistent source canvases are still useful because the trimming process preserves the intended relationship between the artwork and its anchor without retaining transparent padding in memory.&lt;br /&gt;
&lt;br /&gt;
Poor source alignment makes an entity appear to shake, slide, or change height when frames cycle. Per-frame offset changes remain available for deliberate repositioning or assets that cannot be realigned, but they should generally be the exception rather than the primary production workflow.&lt;br /&gt;
&lt;br /&gt;
== Looping ==&lt;br /&gt;
Animations do not loop unless looping is enabled.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
loop {enable} {loop frame - optional} {end frame - optional}&lt;br /&gt;
&lt;br /&gt;
# Default&lt;br /&gt;
loop 0 0 0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;loop &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; repeats the animation. loop &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; disables looping.&lt;br /&gt;
&lt;br /&gt;
The optional loop frame selects the zero-based frame to return to when the animation repeats. The optional end frame is an exclusive boundary - when playback reaches that frame index, it returns to the selected loop frame instead of displaying the boundary frame. &lt;br /&gt;
&lt;br /&gt;
For example, if end frame is &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;, frame &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt; - the sixth frame, does not display - the animation instantly loops on reaching that frame. If end frame is not provided animation proceeds to the last frame and loops.&lt;br /&gt;
&lt;br /&gt;
Idle and walk animations commonly loop. Attacks, pain reactions, falls, and transitions commonly play once and stop on their final frame until native logic or a script selects another animation.&lt;br /&gt;
&lt;br /&gt;
== Delay ==&lt;br /&gt;
&lt;br /&gt;
Delay controls how long a frame remains active before OpenBOR advances the animation. The command is stateful and applies to each following frame until another &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt; command changes it.&lt;br /&gt;
&lt;br /&gt;
=== Syntax ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay &amp;lt;value&amp;gt; [unit]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The unit is optional. Omitting it is equivalent to selecting &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 10&lt;br /&gt;
delay 10 global&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both examples use the module&#039;s global delay unit.&lt;br /&gt;
&lt;br /&gt;
=== Delay units ===&lt;br /&gt;
Optional time unit setting. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Unit&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;&lt;br /&gt;
| Uses the unit selected by &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt;. This is the default mode.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as thousandths of a second.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as hundredths of a second. This is the default global unit and preserves traditional OpenBOR timing.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as seconds.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as minutes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;&lt;br /&gt;
| Stores the value directly as logical clock ticks without time-unit conversion.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
OpenBOR converts the supplied value from the selected unit into logical clock ticks. See [[Animation Overview#Logical clock precision|Logical clock precision]].&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 250 millisecond&lt;br /&gt;
delay 25 centisecond&lt;br /&gt;
delay 2 second&lt;br /&gt;
delay 1 minute&lt;br /&gt;
delay 50 direct&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Singular unit names are required.&lt;br /&gt;
&lt;br /&gt;
=== Global delay unit ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt; to select the unit used by unqualified and explicitly global delays.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
global_config_delay_unit centisecond&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The setting accepts &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;. It does not accept &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, because the global setting must resolve to a concrete unit.&lt;br /&gt;
&lt;br /&gt;
If the command is omitted, the global unit is &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;. Changing the setting reinterprets every model delay that uses &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, so modules should choose a unit deliberately. Explicit frame units remain independent of the global selection.&lt;br /&gt;
&lt;br /&gt;
The setting is read while models are loaded. OpenBOR converts each delay to logical ticks and stores the converted result with the animation frame. It is not a runtime timing switch for animations that are already loaded.&lt;br /&gt;
&lt;br /&gt;
=== Logical clock precision ===&lt;br /&gt;
&lt;br /&gt;
OpenBOR animation timing ultimately uses whole logical clock ticks. Unit conversion follows these relationships:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
millisecond = value * game speed / 1000&lt;br /&gt;
centisecond = value * game speed / 100&lt;br /&gt;
second      = value * game speed&lt;br /&gt;
minute      = value * game speed * 60&lt;br /&gt;
direct      = value&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Fractional results truncate. No time unit can represent a duration smaller than the module&#039;s logical clock precision, and any fractional remainder is discarded.&lt;br /&gt;
&lt;br /&gt;
For example, at the default game speed of 200 ticks per second, each logical tick represents 5 milliseconds. Any value from 15 through 19 milliseconds therefore converts to three ticks, representing 15 milliseconds, while 20 milliseconds converts exactly to four ticks.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt; unit is useful when exact integration with the logical clock matters. Its real-time duration changes with game speed because its value is already a tick count. By pairing a game speed evenly divisible by a target frame rate with direct delays, creators can time animations in exact frame intervals. For example, a game speed of 240 provides four logical ticks for each frame at 60 Hz. This supports frame-based timing conventions commonly used by the fighting game community and fixed-refresh console games.&lt;br /&gt;
&lt;br /&gt;
=== Infinite delay ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or the infinity symbol &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; when a frame should not advance through ordinary clock timing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    delay infinite&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay ∞&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An infinite frame remains active until something outside normal timing changes its animation or frame. Scripts may use the &amp;lt;code&amp;gt;DELAY_INFINITE&amp;lt;/code&amp;gt; constant for the same engine value.&lt;br /&gt;
&lt;br /&gt;
The word &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; is the most portable model-file spelling. The &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; form is UTF-8 text and may be damaged by old Windows editors, archive tools, or other utilities that do not preserve Unicode correctly.&lt;br /&gt;
&lt;br /&gt;
Any negative numeric delay also becomes infinite for backward compatibility. New content should use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; so its intent is explicit.&lt;br /&gt;
&lt;br /&gt;
=== Finite range and behavior flags ===&lt;br /&gt;
&lt;br /&gt;
Finite delay values use the lower 32 bits of an unsigned 64-bit value. The largest finite value is 4,294,967,295 -  in default centisecond units, roughly 16 months. Conversions that would exceed this limit clamp to the largest finite delay instead of overflowing or accidentally becoming infinite.&lt;br /&gt;
&lt;br /&gt;
The upper 32 bits are reserved for delay behavior flags. Bit 63 is the infinite flag. Enhanced-delay calculations modify only the finite lower value and preserve behavior flags, so an infinite frame cannot accidentally become finite.&lt;br /&gt;
&lt;br /&gt;
=== Enhanced delay ===&lt;br /&gt;
&lt;br /&gt;
[[Enhanced Delay|Enhanced delay]], or &amp;lt;code&amp;gt;edelay&amp;lt;/code&amp;gt;, adjusts frame delays through a model&#039;s multiplier, modifier, range, and cap settings. These calculations occur after time-unit conversion, so enhanced-delay ranges, modifiers, and caps operate on logical tick values rather than the source unit written in the animation.&lt;br /&gt;
&lt;br /&gt;
Enhanced-delay arithmetic is bounded to the finite delay range. It cannot overflow into behavior flags or create an infinite delay through calculation.&lt;br /&gt;
&lt;br /&gt;
== Frame properties ==&lt;br /&gt;
&lt;br /&gt;
Timing and graphics form only the minimum animation. Frame property commands can add behavior such as:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;bbox&amp;lt;/code&amp;gt;&lt;br /&gt;
| Defines the frame&#039;s body collision area. Use &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; to disable it.&lt;br /&gt;
|-&lt;br /&gt;
| Attack collision commands&lt;br /&gt;
| Define areas that damage or otherwise affect targets.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;movea&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;movez&amp;lt;/code&amp;gt;&lt;br /&gt;
| Apply frame movement along the model&#039;s axes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;sound&amp;lt;/code&amp;gt;&lt;br /&gt;
| Plays a sound when the frame begins.&lt;br /&gt;
|-&lt;br /&gt;
| Draw commands&lt;br /&gt;
| Change scaling, rotation, transparency, remapping, clipping, and other rendering behavior.&lt;br /&gt;
|-&lt;br /&gt;
| Spawn commands&lt;br /&gt;
| Create or summon child entities on selected frames.&lt;br /&gt;
|-&lt;br /&gt;
| Animation scripts&lt;br /&gt;
| Execute creator-defined logic as frames change.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Property commands normally appear before the &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; they configure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 10&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    bbox none&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame has a body collision box. The second does not because &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; changes the pending frame state before the second &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
== Practical example ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 150 millisecond&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
anim attack1&lt;br /&gt;
    loop 0&lt;br /&gt;
    offset 40 96&lt;br /&gt;
    delay 80 millisecond&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/attack_01.png&lt;br /&gt;
&lt;br /&gt;
    move 4&lt;br /&gt;
    frame data/chars/example/attack_02.png&lt;br /&gt;
&lt;br /&gt;
    move 0&lt;br /&gt;
    delay 160 millisecond&lt;br /&gt;
    frame data/chars/example/attack_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idle animation loops with intentionally uneven timing. The attack plays once, moves the entity during its second frame, stops that movement before the third frame, and holds the final attack pose longer.&lt;br /&gt;
&lt;br /&gt;
== Common problems ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Symptom&lt;br /&gt;
! Likely cause&lt;br /&gt;
|-&lt;br /&gt;
| Frames appear to shake or slide.&lt;br /&gt;
| Offsets do not align the same visual anchor across images.&lt;br /&gt;
|-&lt;br /&gt;
| A property affects the wrong frame.&lt;br /&gt;
| The property command appears after the intended &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line, or persistent frame state was not changed before the next frame.&lt;br /&gt;
|-&lt;br /&gt;
| An animation plays much too quickly or slowly.&lt;br /&gt;
| The delay unit or &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; does not match the values used by the model.&lt;br /&gt;
|-&lt;br /&gt;
| Very small delays appear identical.&lt;br /&gt;
| Both values truncate to the same logical tick count at the selected game speed.&lt;br /&gt;
|-&lt;br /&gt;
| An animation never advances.&lt;br /&gt;
| Its delay is infinite, its animation has stopped, or external logic is holding or replacing its state.&lt;br /&gt;
|-&lt;br /&gt;
| A non-looping animation does not repeat.&lt;br /&gt;
| This is expected unless native logic or a script selects the animation again.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When debugging an animation, begin with a minimal definition containing &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt;. Confirm its images, alignment, and timing before adding collision, movement, effects, or scripts.&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Animation]]&lt;br /&gt;
[[Category:Model]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1105</id>
		<title>Animation Overview</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1105"/>
		<updated>2026-09-27T13:21:21Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Animation identifiers */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Animations are one of OpenBOR&#039;s fundamental building blocks. Models use animations to describe how they look and behave over time. Walking, standing, attacking, falling, taking damage, and many other actions are represented by ordered animation frames.&lt;br /&gt;
&lt;br /&gt;
Each animation belongs to a model and occupies a recognized animation slot such as &amp;lt;code&amp;gt;idle&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;walk&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;attack1&amp;lt;/code&amp;gt;. OpenBOR&#039;s native logic selects many of these slots automatically. Scripts may also select an animation or frame directly.&lt;br /&gt;
&lt;br /&gt;
An animation frame is more than an image. It may also contain timing, offsets, collision boxes, movement, sounds, draw settings, entity spawns, and other behavior.__TOC__&lt;br /&gt;
&lt;br /&gt;
== Basic structure ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; command begins an animation definition. Commands following it configure the animation or prepare properties for its frames. Each &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command adds one frame using the properties currently in effect.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 12&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example creates a looping idle animation with three frames. With the default centisecond delay unit, each frame lasts 0.12 seconds.&lt;br /&gt;
&lt;br /&gt;
Indentation and blank lines are optional, but they make animation blocks easier to read.&lt;br /&gt;
&lt;br /&gt;
=== Animation identifiers ===&lt;br /&gt;
&lt;br /&gt;
The value following &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; identifies the animation&#039;s purpose. Common examples include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Identifier&lt;br /&gt;
! Typical purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;idle&amp;lt;/code&amp;gt;&lt;br /&gt;
| Standing without moving.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;walk&amp;lt;/code&amp;gt;&lt;br /&gt;
| Ordinary movement.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;&lt;br /&gt;
| Initial or rising jump state.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;land&amp;lt;/code&amp;gt;&lt;br /&gt;
| Landing from a jump or fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reacting to damage.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fall&amp;lt;/code&amp;gt;&lt;br /&gt;
| Knockdown or falling reaction.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rise&amp;lt;/code&amp;gt;&lt;br /&gt;
| Recovering from a fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;attack1&amp;lt;/code&amp;gt;&lt;br /&gt;
| A native attack animation.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;freespecial1&amp;lt;/code&amp;gt;&lt;br /&gt;
| A creator-configured special move.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;follow&amp;lt;/code&amp;gt;&lt;br /&gt;
|General use animations inaccessible to native engine logic and only activated by creator defined follow triggers or scripts.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Animation identifiers are behavioral slots, not arbitrary labels. Supplying an animation makes the corresponding behavior available to native engine logic and scripts. Some model types require certain animations to operate normally, while others are optional.&lt;br /&gt;
&lt;br /&gt;
== Frame construction ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR reads model files from top to bottom. Frame property commands update temporary state, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; commits a copy of that state to the animation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    delay 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 25&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame receives a delay of 10. The second receives a delay of 25 because the delay changes before its &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
Commands placed after a &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line cannot alter the frame that was already added. Most frame properties remain active for subsequent frames until another command changes or disables them. Some one-shot properties, such as a frame sound, reset after the frame is added.&lt;br /&gt;
&lt;br /&gt;
Define important state explicitly in each animation. In particular, specifying a delay inside every animation avoids accidentally inheriting a previously active delay while the model file is being parsed.&lt;br /&gt;
=== Frame numbering ===&lt;br /&gt;
Frames use zero-based indexes. The first frame line is frame 0, the second is frame 1, and so on. Commands that target a frame number use these indexes unless their own documentation states otherwise.&lt;br /&gt;
&lt;br /&gt;
The same image may appear more than once when different timing or behavior is needed. OpenBOR reuses its globally cached pixel data, so repeating an image does not require another pixel-data allocation.&lt;br /&gt;
&lt;br /&gt;
Frame &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt; adds a logical frame without a visible sprite, which can be useful for invisible states or script-controlled behavior.&lt;br /&gt;
&lt;br /&gt;
== Offsets ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt; command selects the anchor point inside a frame image.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
offset 32 96&lt;br /&gt;
frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR places the selected point at the entity&#039;s world position. For a standing character, the anchor is usually near the ground between the character&#039;s feet.&lt;br /&gt;
&lt;br /&gt;
Choose a consistent animation canvas size during production and align every source frame to the same anchor before export. Guides for the ground line and horizontal center make this much faster than correcting alignment one frame at a time in the model file.&lt;br /&gt;
&lt;br /&gt;
OpenBOR automatically trims unused image space when it loads sprites. Consistent source canvases are still useful because the trimming process preserves the intended relationship between the artwork and its anchor without retaining transparent padding in memory.&lt;br /&gt;
&lt;br /&gt;
Poor source alignment makes an entity appear to shake, slide, or change height when frames cycle. Per-frame offset changes remain available for deliberate repositioning or assets that cannot be realigned, but they should generally be the exception rather than the primary production workflow.&lt;br /&gt;
&lt;br /&gt;
== Looping ==&lt;br /&gt;
Animations do not loop unless looping is enabled.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
loop {enable} {loop frame - optional} {end frame - optional}&lt;br /&gt;
&lt;br /&gt;
# Default&lt;br /&gt;
loop 0 0 0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;loop &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; repeats the animation. loop &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; disables looping.&lt;br /&gt;
&lt;br /&gt;
The optional loop frame selects the zero-based frame to return to when the animation repeats. The optional end frame is an exclusive boundary - when playback reaches that frame index, it returns to the selected loop frame instead of displaying the boundary frame. &lt;br /&gt;
&lt;br /&gt;
For example, if end frame is &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;, frame &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt; - the sixth frame, does not display - the animation instantly loops on reaching that frame. If end frame is not provided animation proceeds to the last frame and loops.&lt;br /&gt;
&lt;br /&gt;
Idle and walk animations commonly loop. Attacks, pain reactions, falls, and transitions commonly play once and stop on their final frame until native logic or a script selects another animation.&lt;br /&gt;
&lt;br /&gt;
== Delay ==&lt;br /&gt;
&lt;br /&gt;
Delay controls how long a frame remains active before OpenBOR advances the animation. The command is stateful and applies to each following frame until another &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt; command changes it.&lt;br /&gt;
&lt;br /&gt;
=== Syntax ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay &amp;lt;value&amp;gt; [unit]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The unit is optional. Omitting it is equivalent to selecting &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 10&lt;br /&gt;
delay 10 global&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both examples use the module&#039;s global delay unit.&lt;br /&gt;
&lt;br /&gt;
=== Delay units ===&lt;br /&gt;
Optional time unit setting. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Unit&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;&lt;br /&gt;
| Uses the unit selected by &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt;. This is the default mode.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as thousandths of a second.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as hundredths of a second. This is the default global unit and preserves traditional OpenBOR timing.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as seconds.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as minutes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;&lt;br /&gt;
| Stores the value directly as logical clock ticks without time-unit conversion.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
OpenBOR converts the supplied value from the selected unit into logical clock ticks. See [[Animation Overview#Logical clock precision|Logical clock precision]].&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 250 millisecond&lt;br /&gt;
delay 25 centisecond&lt;br /&gt;
delay 2 second&lt;br /&gt;
delay 1 minute&lt;br /&gt;
delay 50 direct&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Singular unit names are required.&lt;br /&gt;
&lt;br /&gt;
=== Global delay unit ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt; to select the unit used by unqualified and explicitly global delays.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
global_config_delay_unit centisecond&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The setting accepts &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;. It does not accept &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, because the global setting must resolve to a concrete unit.&lt;br /&gt;
&lt;br /&gt;
If the command is omitted, the global unit is &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;. Changing the setting reinterprets every model delay that uses &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, so modules should choose a unit deliberately. Explicit frame units remain independent of the global selection.&lt;br /&gt;
&lt;br /&gt;
The setting is read while models are loaded. OpenBOR converts each delay to logical ticks and stores the converted result with the animation frame. It is not a runtime timing switch for animations that are already loaded.&lt;br /&gt;
&lt;br /&gt;
=== Logical clock precision ===&lt;br /&gt;
&lt;br /&gt;
OpenBOR animation timing ultimately uses whole logical clock ticks. Unit conversion follows these relationships:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
millisecond = value * game speed / 1000&lt;br /&gt;
centisecond = value * game speed / 100&lt;br /&gt;
second      = value * game speed&lt;br /&gt;
minute      = value * game speed * 60&lt;br /&gt;
direct      = value&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Fractional results truncate. No time unit can represent a duration smaller than the module&#039;s logical clock precision, and any fractional remainder is discarded.&lt;br /&gt;
&lt;br /&gt;
For example, at the default game speed of 200 ticks per second, each logical tick represents 5 milliseconds. Any value from 15 through 19 milliseconds therefore converts to three ticks, representing 15 milliseconds, while 20 milliseconds converts exactly to four ticks.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt; unit is useful when exact integration with the logical clock matters. Its real-time duration changes with game speed because its value is already a tick count. By pairing a game speed evenly divisible by a target frame rate with direct delays, creators can time animations in exact frame intervals. For example, a game speed of 240 provides four logical ticks for each frame at 60 Hz. This supports frame-based timing conventions commonly used by the fighting game community and fixed-refresh console games.&lt;br /&gt;
&lt;br /&gt;
=== Infinite delay ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or the infinity symbol &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; when a frame should not advance through ordinary clock timing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    delay infinite&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay ∞&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An infinite frame remains active until something outside normal timing changes its animation or frame. Scripts may use the &amp;lt;code&amp;gt;DELAY_INFINITE&amp;lt;/code&amp;gt; constant for the same engine value.&lt;br /&gt;
&lt;br /&gt;
The word &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; is the most portable model-file spelling. The &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; form is UTF-8 text and may be damaged by old Windows editors, archive tools, or other utilities that do not preserve Unicode correctly.&lt;br /&gt;
&lt;br /&gt;
Any negative numeric delay also becomes infinite for backward compatibility. New content should use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; so its intent is explicit.&lt;br /&gt;
&lt;br /&gt;
=== Finite range and behavior flags ===&lt;br /&gt;
&lt;br /&gt;
Finite delay values use the lower 32 bits of an unsigned 64-bit value. The largest finite value is 4,294,967,295 -  in default centisecond units, roughly 16 months. Conversions that would exceed this limit clamp to the largest finite delay instead of overflowing or accidentally becoming infinite.&lt;br /&gt;
&lt;br /&gt;
The upper 32 bits are reserved for delay behavior flags. Bit 63 is the infinite flag. Enhanced-delay calculations modify only the finite lower value and preserve behavior flags, so an infinite frame cannot accidentally become finite.&lt;br /&gt;
&lt;br /&gt;
=== Enhanced delay ===&lt;br /&gt;
&lt;br /&gt;
[[Enhanced Delay|Enhanced delay]], or &amp;lt;code&amp;gt;edelay&amp;lt;/code&amp;gt;, adjusts frame delays through a model&#039;s multiplier, modifier, range, and cap settings. These calculations occur after time-unit conversion, so enhanced-delay ranges, modifiers, and caps operate on logical tick values rather than the source unit written in the animation.&lt;br /&gt;
&lt;br /&gt;
Enhanced-delay arithmetic is bounded to the finite delay range. It cannot overflow into behavior flags or create an infinite delay through calculation.&lt;br /&gt;
&lt;br /&gt;
== Frame properties ==&lt;br /&gt;
&lt;br /&gt;
Timing and graphics form only the minimum animation. Frame property commands can add behavior such as:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;bbox&amp;lt;/code&amp;gt;&lt;br /&gt;
| Defines the frame&#039;s body collision area. Use &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; to disable it.&lt;br /&gt;
|-&lt;br /&gt;
| Attack collision commands&lt;br /&gt;
| Define areas that damage or otherwise affect targets.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;movea&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;movez&amp;lt;/code&amp;gt;&lt;br /&gt;
| Apply frame movement along the model&#039;s axes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;sound&amp;lt;/code&amp;gt;&lt;br /&gt;
| Plays a sound when the frame begins.&lt;br /&gt;
|-&lt;br /&gt;
| Draw commands&lt;br /&gt;
| Change scaling, rotation, transparency, remapping, clipping, and other rendering behavior.&lt;br /&gt;
|-&lt;br /&gt;
| Spawn commands&lt;br /&gt;
| Create or summon child entities on selected frames.&lt;br /&gt;
|-&lt;br /&gt;
| Animation scripts&lt;br /&gt;
| Execute creator-defined logic as frames change.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Property commands normally appear before the &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; they configure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 10&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    bbox none&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame has a body collision box. The second does not because &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; changes the pending frame state before the second &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
== Practical example ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 150 millisecond&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
anim attack1&lt;br /&gt;
    loop 0&lt;br /&gt;
    offset 40 96&lt;br /&gt;
    delay 80 millisecond&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/attack_01.png&lt;br /&gt;
&lt;br /&gt;
    move 4&lt;br /&gt;
    frame data/chars/example/attack_02.png&lt;br /&gt;
&lt;br /&gt;
    move 0&lt;br /&gt;
    delay 160 millisecond&lt;br /&gt;
    frame data/chars/example/attack_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idle animation loops with intentionally uneven timing. The attack plays once, moves the entity during its second frame, stops that movement before the third frame, and holds the final attack pose longer.&lt;br /&gt;
&lt;br /&gt;
== Common problems ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Symptom&lt;br /&gt;
! Likely cause&lt;br /&gt;
|-&lt;br /&gt;
| Frames appear to shake or slide.&lt;br /&gt;
| Offsets do not align the same visual anchor across images.&lt;br /&gt;
|-&lt;br /&gt;
| A property affects the wrong frame.&lt;br /&gt;
| The property command appears after the intended &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line, or persistent frame state was not changed before the next frame.&lt;br /&gt;
|-&lt;br /&gt;
| An animation plays much too quickly or slowly.&lt;br /&gt;
| The delay unit or &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; does not match the values used by the model.&lt;br /&gt;
|-&lt;br /&gt;
| Very small delays appear identical.&lt;br /&gt;
| Both values truncate to the same logical tick count at the selected game speed.&lt;br /&gt;
|-&lt;br /&gt;
| An animation never advances.&lt;br /&gt;
| Its delay is infinite, its animation has stopped, or external logic is holding or replacing its state.&lt;br /&gt;
|-&lt;br /&gt;
| A non-looping animation does not repeat.&lt;br /&gt;
| This is expected unless native logic or a script selects the animation again.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When debugging an animation, begin with a minimal definition containing &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt;. Confirm its images, alignment, and timing before adding collision, movement, effects, or scripts.&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Animation]]&lt;br /&gt;
[[Category:Model]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1104</id>
		<title>Animation Overview</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1104"/>
		<updated>2026-09-27T13:17:01Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Finite range and behavior flags */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Animations are one of OpenBOR&#039;s fundamental building blocks. Models use animations to describe how they look and behave over time. Walking, standing, attacking, falling, taking damage, and many other actions are represented by ordered animation frames.&lt;br /&gt;
&lt;br /&gt;
Each animation belongs to a model and occupies a recognized animation slot such as &amp;lt;code&amp;gt;idle&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;walk&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;attack1&amp;lt;/code&amp;gt;. OpenBOR&#039;s native logic selects many of these slots automatically. Scripts may also select an animation or frame directly.&lt;br /&gt;
&lt;br /&gt;
An animation frame is more than an image. It may also contain timing, offsets, collision boxes, movement, sounds, draw settings, entity spawns, and other behavior.__TOC__&lt;br /&gt;
&lt;br /&gt;
== Basic structure ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; command begins an animation definition. Commands following it configure the animation or prepare properties for its frames. Each &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command adds one frame using the properties currently in effect.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 12&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example creates a looping idle animation with three frames. With the default centisecond delay unit, each frame lasts 0.12 seconds.&lt;br /&gt;
&lt;br /&gt;
Indentation and blank lines are optional, but they make animation blocks easier to read.&lt;br /&gt;
&lt;br /&gt;
=== Animation identifiers ===&lt;br /&gt;
&lt;br /&gt;
The value following &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; identifies the animation&#039;s purpose. Common examples include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Identifier&lt;br /&gt;
! Typical purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;idle&amp;lt;/code&amp;gt;&lt;br /&gt;
| Standing without moving.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;walk&amp;lt;/code&amp;gt;&lt;br /&gt;
| Ordinary movement.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;&lt;br /&gt;
| Initial or rising jump state.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;land&amp;lt;/code&amp;gt;&lt;br /&gt;
| Landing from a jump or fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reacting to damage.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fall&amp;lt;/code&amp;gt;&lt;br /&gt;
| Knockdown or falling reaction.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rise&amp;lt;/code&amp;gt;&lt;br /&gt;
| Recovering from a fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;attack1&amp;lt;/code&amp;gt;&lt;br /&gt;
| A native attack animation.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;freespecial1&amp;lt;/code&amp;gt;&lt;br /&gt;
| A creator-configured special move.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Animation identifiers are behavioral slots, not arbitrary labels. Supplying an animation makes the corresponding behavior available to native engine logic and scripts. Some model types require certain animations to operate normally, while others are optional.&lt;br /&gt;
&lt;br /&gt;
== Frame construction ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR reads model files from top to bottom. Frame property commands update temporary state, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; commits a copy of that state to the animation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    delay 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 25&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame receives a delay of 10. The second receives a delay of 25 because the delay changes before its &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
Commands placed after a &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line cannot alter the frame that was already added. Most frame properties remain active for subsequent frames until another command changes or disables them. Some one-shot properties, such as a frame sound, reset after the frame is added.&lt;br /&gt;
&lt;br /&gt;
Define important state explicitly in each animation. In particular, specifying a delay inside every animation avoids accidentally inheriting a previously active delay while the model file is being parsed.&lt;br /&gt;
=== Frame numbering ===&lt;br /&gt;
Frames use zero-based indexes. The first frame line is frame 0, the second is frame 1, and so on. Commands that target a frame number use these indexes unless their own documentation states otherwise.&lt;br /&gt;
&lt;br /&gt;
The same image may appear more than once when different timing or behavior is needed. OpenBOR reuses its globally cached pixel data, so repeating an image does not require another pixel-data allocation.&lt;br /&gt;
&lt;br /&gt;
Frame &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt; adds a logical frame without a visible sprite, which can be useful for invisible states or script-controlled behavior.&lt;br /&gt;
&lt;br /&gt;
== Offsets ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt; command selects the anchor point inside a frame image.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
offset 32 96&lt;br /&gt;
frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR places the selected point at the entity&#039;s world position. For a standing character, the anchor is usually near the ground between the character&#039;s feet.&lt;br /&gt;
&lt;br /&gt;
Choose a consistent animation canvas size during production and align every source frame to the same anchor before export. Guides for the ground line and horizontal center make this much faster than correcting alignment one frame at a time in the model file.&lt;br /&gt;
&lt;br /&gt;
OpenBOR automatically trims unused image space when it loads sprites. Consistent source canvases are still useful because the trimming process preserves the intended relationship between the artwork and its anchor without retaining transparent padding in memory.&lt;br /&gt;
&lt;br /&gt;
Poor source alignment makes an entity appear to shake, slide, or change height when frames cycle. Per-frame offset changes remain available for deliberate repositioning or assets that cannot be realigned, but they should generally be the exception rather than the primary production workflow.&lt;br /&gt;
&lt;br /&gt;
== Looping ==&lt;br /&gt;
Animations do not loop unless looping is enabled.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
loop {enable} {loop frame - optional} {end frame - optional}&lt;br /&gt;
&lt;br /&gt;
# Default&lt;br /&gt;
loop 0 0 0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;loop &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; repeats the animation. loop &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; disables looping.&lt;br /&gt;
&lt;br /&gt;
The optional loop frame selects the zero-based frame to return to when the animation repeats. The optional end frame is an exclusive boundary - when playback reaches that frame index, it returns to the selected loop frame instead of displaying the boundary frame. &lt;br /&gt;
&lt;br /&gt;
For example, if end frame is &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;, frame &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt; - the sixth frame, does not display - the animation instantly loops on reaching that frame. If end frame is not provided animation proceeds to the last frame and loops.&lt;br /&gt;
&lt;br /&gt;
Idle and walk animations commonly loop. Attacks, pain reactions, falls, and transitions commonly play once and stop on their final frame until native logic or a script selects another animation.&lt;br /&gt;
&lt;br /&gt;
== Delay ==&lt;br /&gt;
&lt;br /&gt;
Delay controls how long a frame remains active before OpenBOR advances the animation. The command is stateful and applies to each following frame until another &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt; command changes it.&lt;br /&gt;
&lt;br /&gt;
=== Syntax ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay &amp;lt;value&amp;gt; [unit]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The unit is optional. Omitting it is equivalent to selecting &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 10&lt;br /&gt;
delay 10 global&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both examples use the module&#039;s global delay unit.&lt;br /&gt;
&lt;br /&gt;
=== Delay units ===&lt;br /&gt;
Optional time unit setting. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Unit&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;&lt;br /&gt;
| Uses the unit selected by &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt;. This is the default mode.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as thousandths of a second.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as hundredths of a second. This is the default global unit and preserves traditional OpenBOR timing.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as seconds.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as minutes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;&lt;br /&gt;
| Stores the value directly as logical clock ticks without time-unit conversion.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
OpenBOR converts the supplied value from the selected unit into logical clock ticks. See [[Animation Overview#Logical clock precision|Logical clock precision]].&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 250 millisecond&lt;br /&gt;
delay 25 centisecond&lt;br /&gt;
delay 2 second&lt;br /&gt;
delay 1 minute&lt;br /&gt;
delay 50 direct&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Singular unit names are required.&lt;br /&gt;
&lt;br /&gt;
=== Global delay unit ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt; to select the unit used by unqualified and explicitly global delays.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
global_config_delay_unit centisecond&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The setting accepts &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;. It does not accept &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, because the global setting must resolve to a concrete unit.&lt;br /&gt;
&lt;br /&gt;
If the command is omitted, the global unit is &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;. Changing the setting reinterprets every model delay that uses &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, so modules should choose a unit deliberately. Explicit frame units remain independent of the global selection.&lt;br /&gt;
&lt;br /&gt;
The setting is read while models are loaded. OpenBOR converts each delay to logical ticks and stores the converted result with the animation frame. It is not a runtime timing switch for animations that are already loaded.&lt;br /&gt;
&lt;br /&gt;
=== Logical clock precision ===&lt;br /&gt;
&lt;br /&gt;
OpenBOR animation timing ultimately uses whole logical clock ticks. Unit conversion follows these relationships:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
millisecond = value * game speed / 1000&lt;br /&gt;
centisecond = value * game speed / 100&lt;br /&gt;
second      = value * game speed&lt;br /&gt;
minute      = value * game speed * 60&lt;br /&gt;
direct      = value&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Fractional results truncate. No time unit can represent a duration smaller than the module&#039;s logical clock precision, and any fractional remainder is discarded.&lt;br /&gt;
&lt;br /&gt;
For example, at the default game speed of 200 ticks per second, each logical tick represents 5 milliseconds. Any value from 15 through 19 milliseconds therefore converts to three ticks, representing 15 milliseconds, while 20 milliseconds converts exactly to four ticks.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt; unit is useful when exact integration with the logical clock matters. Its real-time duration changes with game speed because its value is already a tick count. By pairing a game speed evenly divisible by a target frame rate with direct delays, creators can time animations in exact frame intervals. For example, a game speed of 240 provides four logical ticks for each frame at 60 Hz. This supports frame-based timing conventions commonly used by the fighting game community and fixed-refresh console games.&lt;br /&gt;
&lt;br /&gt;
=== Infinite delay ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or the infinity symbol &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; when a frame should not advance through ordinary clock timing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    delay infinite&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay ∞&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An infinite frame remains active until something outside normal timing changes its animation or frame. Scripts may use the &amp;lt;code&amp;gt;DELAY_INFINITE&amp;lt;/code&amp;gt; constant for the same engine value.&lt;br /&gt;
&lt;br /&gt;
The word &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; is the most portable model-file spelling. The &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; form is UTF-8 text and may be damaged by old Windows editors, archive tools, or other utilities that do not preserve Unicode correctly.&lt;br /&gt;
&lt;br /&gt;
Any negative numeric delay also becomes infinite for backward compatibility. New content should use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; so its intent is explicit.&lt;br /&gt;
&lt;br /&gt;
=== Finite range and behavior flags ===&lt;br /&gt;
&lt;br /&gt;
Finite delay values use the lower 32 bits of an unsigned 64-bit value. The largest finite value is 4,294,967,295 -  in default centisecond units, roughly 16 months. Conversions that would exceed this limit clamp to the largest finite delay instead of overflowing or accidentally becoming infinite.&lt;br /&gt;
&lt;br /&gt;
The upper 32 bits are reserved for delay behavior flags. Bit 63 is the infinite flag. Enhanced-delay calculations modify only the finite lower value and preserve behavior flags, so an infinite frame cannot accidentally become finite.&lt;br /&gt;
&lt;br /&gt;
=== Enhanced delay ===&lt;br /&gt;
&lt;br /&gt;
[[Enhanced Delay|Enhanced delay]], or &amp;lt;code&amp;gt;edelay&amp;lt;/code&amp;gt;, adjusts frame delays through a model&#039;s multiplier, modifier, range, and cap settings. These calculations occur after time-unit conversion, so enhanced-delay ranges, modifiers, and caps operate on logical tick values rather than the source unit written in the animation.&lt;br /&gt;
&lt;br /&gt;
Enhanced-delay arithmetic is bounded to the finite delay range. It cannot overflow into behavior flags or create an infinite delay through calculation.&lt;br /&gt;
&lt;br /&gt;
== Frame properties ==&lt;br /&gt;
&lt;br /&gt;
Timing and graphics form only the minimum animation. Frame property commands can add behavior such as:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;bbox&amp;lt;/code&amp;gt;&lt;br /&gt;
| Defines the frame&#039;s body collision area. Use &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; to disable it.&lt;br /&gt;
|-&lt;br /&gt;
| Attack collision commands&lt;br /&gt;
| Define areas that damage or otherwise affect targets.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;movea&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;movez&amp;lt;/code&amp;gt;&lt;br /&gt;
| Apply frame movement along the model&#039;s axes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;sound&amp;lt;/code&amp;gt;&lt;br /&gt;
| Plays a sound when the frame begins.&lt;br /&gt;
|-&lt;br /&gt;
| Draw commands&lt;br /&gt;
| Change scaling, rotation, transparency, remapping, clipping, and other rendering behavior.&lt;br /&gt;
|-&lt;br /&gt;
| Spawn commands&lt;br /&gt;
| Create or summon child entities on selected frames.&lt;br /&gt;
|-&lt;br /&gt;
| Animation scripts&lt;br /&gt;
| Execute creator-defined logic as frames change.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Property commands normally appear before the &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; they configure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 10&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    bbox none&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame has a body collision box. The second does not because &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; changes the pending frame state before the second &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
== Practical example ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 150 millisecond&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
anim attack1&lt;br /&gt;
    loop 0&lt;br /&gt;
    offset 40 96&lt;br /&gt;
    delay 80 millisecond&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/attack_01.png&lt;br /&gt;
&lt;br /&gt;
    move 4&lt;br /&gt;
    frame data/chars/example/attack_02.png&lt;br /&gt;
&lt;br /&gt;
    move 0&lt;br /&gt;
    delay 160 millisecond&lt;br /&gt;
    frame data/chars/example/attack_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idle animation loops with intentionally uneven timing. The attack plays once, moves the entity during its second frame, stops that movement before the third frame, and holds the final attack pose longer.&lt;br /&gt;
&lt;br /&gt;
== Common problems ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Symptom&lt;br /&gt;
! Likely cause&lt;br /&gt;
|-&lt;br /&gt;
| Frames appear to shake or slide.&lt;br /&gt;
| Offsets do not align the same visual anchor across images.&lt;br /&gt;
|-&lt;br /&gt;
| A property affects the wrong frame.&lt;br /&gt;
| The property command appears after the intended &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line, or persistent frame state was not changed before the next frame.&lt;br /&gt;
|-&lt;br /&gt;
| An animation plays much too quickly or slowly.&lt;br /&gt;
| The delay unit or &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; does not match the values used by the model.&lt;br /&gt;
|-&lt;br /&gt;
| Very small delays appear identical.&lt;br /&gt;
| Both values truncate to the same logical tick count at the selected game speed.&lt;br /&gt;
|-&lt;br /&gt;
| An animation never advances.&lt;br /&gt;
| Its delay is infinite, its animation has stopped, or external logic is holding or replacing its state.&lt;br /&gt;
|-&lt;br /&gt;
| A non-looping animation does not repeat.&lt;br /&gt;
| This is expected unless native logic or a script selects the animation again.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When debugging an animation, begin with a minimal definition containing &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt;. Confirm its images, alignment, and timing before adding collision, movement, effects, or scripts.&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Animation]]&lt;br /&gt;
[[Category:Model]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Rgbcolor()&amp;diff=1103</id>
		<title>Rgbcolor()</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Rgbcolor()&amp;diff=1103"/>
		<updated>2026-09-27T04:47:13Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Direct Drawing#Colors and blend modes&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Direct Drawing#Colors and blend modes]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Script Functions]]&lt;br /&gt;
[[Category:Graphics]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=OpenBOR_Overview&amp;diff=1100</id>
		<title>OpenBOR Overview</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=OpenBOR_Overview&amp;diff=1100"/>
		<updated>2026-09-11T18:33:04Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Animations */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;This article provides a general overview of how the OpenBOR engine organizes and operates a game module. Individual systems and commands are covered in their respective articles.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Rule 0:&#039;&#039;&#039; OpenBOR’s native behaviors are starting defaults, not immutable rules. Virtually every system can be adjusted, extended, or replaced through configuration and script.&lt;br /&gt;
&lt;br /&gt;
== Engine Structure ==&lt;br /&gt;
An OpenBOR game consists of two principal components:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Engine&#039;&#039;&#039; – The executable and supporting platform files that provide OpenBOR’s runtime systems.&lt;br /&gt;
* &#039;&#039;&#039;Module&#039;&#039;&#039; – The game’s configuration, models, levels, scripts, graphics, and audio.&lt;br /&gt;
&lt;br /&gt;
During development, a module may be loaded directly from an unpacked &amp;lt;code&amp;gt;data&amp;lt;/code&amp;gt; directory. For distribution, the same directory can be packaged into a &amp;lt;code&amp;gt;.pak&amp;lt;/code&amp;gt; file.&lt;br /&gt;
&lt;br /&gt;
Packaged game modules may contain up to 2 GB of data in the legacy pack format, or about 9 exabytes in the modern 64-bit format. &lt;br /&gt;
&lt;br /&gt;
== Module Layout ==&lt;br /&gt;
A typical unpacked module has the following layout:&amp;lt;pre&amp;gt;&lt;br /&gt;
data/&lt;br /&gt;
├── models.txt&lt;br /&gt;
├── levels.txt&lt;br /&gt;
├── video.txt&lt;br /&gt;
├── lifebar.txt&lt;br /&gt;
├── menu.txt&lt;br /&gt;
├── script.txt&lt;br /&gt;
├── bgs/&lt;br /&gt;
├── chars/&lt;br /&gt;
├── levels/&lt;br /&gt;
├── music/&lt;br /&gt;
├── scenes/&lt;br /&gt;
├── sounds/&lt;br /&gt;
├── sprites/&lt;br /&gt;
└── scripts/&lt;br /&gt;
&amp;lt;/pre&amp;gt;The subdirectory names are organizational conventions. Creators may use any directory structure they prefer as long as referenced paths are correct. &lt;br /&gt;
&lt;br /&gt;
The principal root files are:&lt;br /&gt;
&lt;br /&gt;
* [[Model Registry|models.txt]] – Registers entity models, controls how they are loaded, and provides several model-related module settings.&lt;br /&gt;
* &amp;lt;code&amp;gt;levels.txt&amp;lt;/code&amp;gt; – Defines level sets, game modes, stage order, and several general level and interface settings.&lt;br /&gt;
* &amp;lt;code&amp;gt;video.txt&amp;lt;/code&amp;gt; – Optional display and video configuration.&lt;br /&gt;
* &amp;lt;code&amp;gt;lifebar.txt&amp;lt;/code&amp;gt; – Optional lifebar presentation configuration.&lt;br /&gt;
* &amp;lt;code&amp;gt;menu.txt&amp;lt;/code&amp;gt; – Optional menu configuration.&lt;br /&gt;
* &amp;lt;code&amp;gt;script.txt&amp;lt;/code&amp;gt; – Optional scripting limits and global script behavior.&lt;br /&gt;
Do not be misled by the use of .txt format. OpenBOR’s configuration files are declarative object and asset definitions, not a linear instruction stream. The engine parses them into structured runtime data used by native systems and scripts.&lt;br /&gt;
&lt;br /&gt;
== Models and Entities ==&lt;br /&gt;
A &#039;&#039;&#039;model&#039;&#039;&#039; is a template describing an object available to the engine. Players, enemies, items, obstacles, projectiles, effects, and many utility objects are all models.&lt;br /&gt;
&lt;br /&gt;
An &#039;&#039;&#039;entity&#039;&#039;&#039; is a runtime instance of a model. When OpenBOR spawns a model, it creates an entity and copies the model’s initial properties into it. Native logic and scripts may then modify that entity independently.&lt;br /&gt;
&lt;br /&gt;
Model files may define statistics, movement, artificial intelligence, animations, attacks, defense, collision, palettes, sounds, projectiles, and event scripts.&lt;br /&gt;
&lt;br /&gt;
See [[Models]] and [[Entities]].&lt;br /&gt;
&lt;br /&gt;
== Animations ==&lt;br /&gt;
Animations organize an entity’s visual frames, timing, and actions. OpenBOR recognizes animation types such as &amp;lt;code&amp;gt;IDLE&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;WALK&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;ATTACK&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;PAIN&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;FALL&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;RISE&amp;lt;/code&amp;gt; and uses them to control common entity behavior.&lt;br /&gt;
&lt;br /&gt;
Animation data may include:&lt;br /&gt;
&lt;br /&gt;
* Sprite frames and delays.&lt;br /&gt;
* Entity-origin offsets.&lt;br /&gt;
* Attack and body collision boxes.&lt;br /&gt;
* Movement and velocity instructions.&lt;br /&gt;
* Sounds and visual effects.&lt;br /&gt;
* Spawned entities.&lt;br /&gt;
* Frame-specific commands and scripts.&lt;br /&gt;
&lt;br /&gt;
Animations therefore serve as both visual sequences and configurable entity states.&lt;br /&gt;
&lt;br /&gt;
See [[Animation Overview|Animations]].&lt;br /&gt;
&lt;br /&gt;
== Levels and Level Sets ==&lt;br /&gt;
A &#039;&#039;&#039;level&#039;&#039;&#039; describes a playable area. Level files arrange backgrounds, terrain, boundaries, spawn points, music, environmental objects, and other stage content.&lt;br /&gt;
&lt;br /&gt;
A &#039;&#039;&#039;level set&#039;&#039;&#039; is an ordered collection of levels and scenes declared in &amp;lt;code&amp;gt;levels.txt&amp;lt;/code&amp;gt;. Level sets may represent game modes, routes, episodes, or difficulty selections.&lt;br /&gt;
&lt;br /&gt;
See [[Levels]], [[Level Sets]], and [[Scenes]].&lt;br /&gt;
&lt;br /&gt;
== Scenes and Media Playback ==&lt;br /&gt;
OpenBOR provides native facilities for assembling audiovisual scenes and presenting prerecorded media. The scene system can combine music with animated .gif graphics, while .webm playback provides streamed full-motion video with no length or size limitations.&lt;br /&gt;
&lt;br /&gt;
These features are not limited to transitions or narrative cutscenes. Creators may use them for introductions, presentations, galleries, continuous media playback, or as a module’s primary content. OpenBOR may therefore function as a general multimedia framework or even a dedicated video player.&lt;br /&gt;
&lt;br /&gt;
See [[Scenes]] and [[WebM]].&lt;br /&gt;
&lt;br /&gt;
== Game World ==&lt;br /&gt;
OpenBOR places entities in a three-dimensional Cartesian game field:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;X&#039;&#039;&#039; – Horizontal position.&lt;br /&gt;
* &#039;&#039;&#039;Y&#039;&#039;&#039; – Height.&lt;br /&gt;
* &#039;&#039;&#039;Z&#039;&#039;&#039; – Lateral depth toward or away from the camera.&lt;br /&gt;
* &#039;&#039;&#039;Base&#039;&#039;&#039; – The height on which an entity currently stands.&lt;br /&gt;
&lt;br /&gt;
The game world supports floating-point positions and velocity. OpenBOR projects these positions onto the two-dimensional display while managing screen placement, depth sorting, and smooth subpixel movement. For strictly 2D environments, creators may set Z position limits to identical values, and the engine will natively invoke two-dimensional world behaviors, including control maps like ducking.&lt;br /&gt;
&lt;br /&gt;
See [[Geometry Overview]].&lt;br /&gt;
&lt;br /&gt;
== Collision and Combat ==&lt;br /&gt;
Collision boxes describe the physical areas used for attacks, damage reception, terrain, and entity contact.&lt;br /&gt;
&lt;br /&gt;
Attack properties determine the initial force and behavior of a hit. The attacker’s offense properties modify its damage output, after which the target’s defense properties modify the incoming result. Other properties control pain, knockdown, blocking, effects, and death behavior. &lt;br /&gt;
&lt;br /&gt;
* [[Collision]] - Defining collision boxes, including body (hurt) and attack.&lt;br /&gt;
* [[Attack Type|Attack Types]] - Native reactive system to differing attack types.&lt;br /&gt;
* [[Damage Control]] - Damage mitigation - defense and offense - for differing attack types.&lt;br /&gt;
&lt;br /&gt;
== Native Functionality ==&lt;br /&gt;
OpenBOR provides native systems for common game functionality, including:&lt;br /&gt;
&lt;br /&gt;
* Player input and control mapping.&lt;br /&gt;
* Movement, jumping, gravity, and platforms.&lt;br /&gt;
* Enemy targeting and artificial intelligence.&lt;br /&gt;
* Attacks, blocking, grabbing, throwing, and counters.&lt;br /&gt;
* Damage, pain, knockdown, and death.&lt;br /&gt;
* Projectiles, weapons, items, and summoned entities.&lt;br /&gt;
* Cameras, scrolling, layers, palettes, and shadows.&lt;br /&gt;
* Menus, player selection, HUD elements, and saved progress.&lt;br /&gt;
* Music and sound effects.&lt;br /&gt;
&lt;br /&gt;
Creators may use these systems directly, combine their settings into new behaviors, or modify them through script.&lt;br /&gt;
&lt;br /&gt;
== Time ==&lt;br /&gt;
OpenBOR separates gameplay timing from display updates. Its logical clock runs at 200 ticks per second by default and drives entity simulation, animations, effects, and most game logic, while the outer update and rendering loop may run independently at the selected frame rate. Scripts may also access host system time for real-world dates and timestamps. In addition, a traditional game countdown timer is enabled by default, beginning at 99 and decrementing once every two seconds, but creators may configure, hide, disable, or replace it.&lt;br /&gt;
&lt;br /&gt;
See [[Time]].&lt;br /&gt;
&lt;br /&gt;
== Scripting ==&lt;br /&gt;
OpenBOR includes a C-like scripting engine for functionality beyond native configuration. Scripts can execute in response to engine updates, animation frames, input, collisions, damage, entity events, level events, and many other conditions.&lt;br /&gt;
&lt;br /&gt;
Scripts may inspect or modify engine objects, implement custom mechanics, draw graphics, control audio, manage data, and create reusable function libraries. See the following. &lt;br /&gt;
&lt;br /&gt;
* [[Script Overview]] - Detailed overview of script syntax and operators.&lt;br /&gt;
* [[:Category:Script Functions|Script Functions]] - List of native script functions.&lt;br /&gt;
* [[Variables Overview|Variables]] - Variable types and implications. &lt;br /&gt;
* [[Strings]] - String specific functions.&lt;br /&gt;
* [[Arrays]] - Arracy ad linked list API.&lt;br /&gt;
* [[File Operations]] - File CRUD API.&lt;br /&gt;
&lt;br /&gt;
== Runtime Layout ==&lt;br /&gt;
&lt;br /&gt;
Module files provide the engine with models, levels, assets, and scripts. OpenBOR uses this information to construct the runtime game world, then continuously processes input, game logic, entity behavior, collision, and drawing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
                       MODULE DATA&lt;br /&gt;
       ┌──────────────────┼──────────────────┐&lt;br /&gt;
       │                  │                  │&lt;br /&gt;
  models.txt         levels.txt        Assets and scripts&lt;br /&gt;
       │                  │                  │&lt;br /&gt;
       ▼                  ▼                  ▼&lt;br /&gt;
  Model files       Levels and scenes   Graphics, audio,&lt;br /&gt;
       │                                and custom logic&lt;br /&gt;
       └──────────────────┬──────────────────┘&lt;br /&gt;
                          ▼&lt;br /&gt;
                    RUNTIME WORLD&lt;br /&gt;
                          │&lt;br /&gt;
                          ▼&lt;br /&gt;
┌──────────────── REPEATING ENGINE CYCLE ────────────────┐&lt;br /&gt;
│                                                        │&lt;br /&gt;
│  Read player input                                     │&lt;br /&gt;
│    └─ Input and key script hooks                       │&lt;br /&gt;
│                                                        │&lt;br /&gt;
│  Run global and level update scripts                   │&lt;br /&gt;
│                                                        │&lt;br /&gt;
│  Update entities                                       │&lt;br /&gt;
│    ├─ Entity update and think script hooks             │&lt;br /&gt;
│    ├─ Player control and artificial intelligence       │&lt;br /&gt;
│    ├─ Animation and frame script hooks                 │&lt;br /&gt;
│    ├─ Movement, gravity, and terrain                   │&lt;br /&gt;
│    └─ Collision, attacks, damage, and reaction hooks   │&lt;br /&gt;
│                                                        │&lt;br /&gt;
│  Queue graphics                                        │&lt;br /&gt;
│    └─ Entity drawing script hooks                      │&lt;br /&gt;
│                                                        │&lt;br /&gt;
│  Run post-update scripts                               │&lt;br /&gt;
│                                                        │&lt;br /&gt;
│  Draw the completed frame and continue                 │&lt;br /&gt;
│                                                        │&lt;br /&gt;
└────────────────────────────────────────────────────────┘&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Script entry points are distributed throughout the runtime and may execute before, during, or after many engine operations. Hooks are available for input, updates, animation frames, movement, collisions, attacks, blocking, damage, spawning, drawing, death, level events, and numerous other conditions.&lt;br /&gt;
&lt;br /&gt;
When no script is supplied, OpenBOR performs its native behavior normally. Creators may therefore use the engine as provided, alter selected events, or replace large portions of the runtime with custom logic. This integration between native systems and event scripts is the basis of OpenBOR’s extensibility.&lt;br /&gt;
&lt;br /&gt;
== Graphic Layout ==&lt;br /&gt;
OpenBOR’s native graphics pipeline uses WYSIWYG image assets that are automatically prepared and optimized for runtime. Background layers, sprites, shadows, effects, interface elements, and script-created subscreens are composited through a depth-sorted draw queue, with native support for effects like palette remapping, transparency, blending, scaling, rotation, and clipping. Script hooks allow creators to customize or replace rendering behavior throughout the pipeline. See [[Graphics Overview]].&lt;br /&gt;
&lt;br /&gt;
As of OpenBOR 4.x, all rendering and composition prior to the final &amp;lt;code&amp;gt;video_copy_screen()&amp;lt;/code&amp;gt; presentation step are performed in software. This architecture is a legacy of OpenBOR’s historically broad platform support and is being phased out in favor of hardware-accelerated rendering.&amp;lt;pre&amp;gt;&lt;br /&gt;
Graphics Sources&lt;br /&gt;
│&lt;br /&gt;
├── Loaded Assets&lt;br /&gt;
│   ├── Animation frames and sprites&lt;br /&gt;
│   ├── Level backgrounds and graphic layers&lt;br /&gt;
│   ├── Fonts and interface graphics&lt;br /&gt;
│   └── Menu and scene graphics&lt;br /&gt;
│&lt;br /&gt;
├── Native Engine Rendering&lt;br /&gt;
│   ├── Level layers and terrain&lt;br /&gt;
│   ├── Entities, shadows, and effects&lt;br /&gt;
│   ├── HUD and status displays&lt;br /&gt;
│   └── Menus, text, and overlays&lt;br /&gt;
│&lt;br /&gt;
└── Script Rendering&lt;br /&gt;
    ├── Sprites&lt;br /&gt;
    ├── Text&lt;br /&gt;
    ├── Lines, boxes, and other primitives&lt;br /&gt;
    └── Subscreen surfaces (s_screen)&lt;br /&gt;
        ├── Allocate an off-screen surface&lt;br /&gt;
        ├── Draw graphics onto the surface&lt;br /&gt;
        ├── Combine multiple elements or surfaces&lt;br /&gt;
        └── Queue the completed surface with drawscreen()&lt;br /&gt;
                         │&lt;br /&gt;
                         ▼&lt;br /&gt;
                  Sprite/Draw Queue&lt;br /&gt;
                         │&lt;br /&gt;
                         ├── Layer and Z ordering&lt;br /&gt;
                         ├── Position and clipping&lt;br /&gt;
                         ├── Palette and remap&lt;br /&gt;
                         ├── Transparency and blending&lt;br /&gt;
                         └── Scaling, rotation, and other draw methods&lt;br /&gt;
                         │&lt;br /&gt;
                         ▼&lt;br /&gt;
             Main Virtual Screen (vscreen)&lt;br /&gt;
             ─────────────────────────────&lt;br /&gt;
             Queue contents are composited&lt;br /&gt;
             into the current video frame.&lt;br /&gt;
                         │&lt;br /&gt;
                         ├── Optional screenshot capture&lt;br /&gt;
                         └── Debug and final overlays&lt;br /&gt;
                         │&lt;br /&gt;
                         ▼&lt;br /&gt;
                  video_copy_screen()&lt;br /&gt;
                         │&lt;br /&gt;
                         ├── Output scaling&lt;br /&gt;
                         ├── Video filtering&lt;br /&gt;
                         ├── Color adjustment&lt;br /&gt;
                         └── Window or fullscreen presentation&lt;br /&gt;
                         │&lt;br /&gt;
                         ▼&lt;br /&gt;
                   Physical Display&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[Category:Openbor]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Time&amp;diff=1099</id>
		<title>Time</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Time&amp;diff=1099"/>
		<updated>2026-08-26T15:29:24Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Introduction */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
Timing is crucial to OpenBOR, as it is to most game engines. Understanding how the engine measures time, and how those measurements affect a project, is therefore essential.&lt;br /&gt;
&lt;br /&gt;
OpenBOR uses a modern decoupled timing model by default. This means gameplay simulation and display rendering operate as independent loops, with the simulation advancing at regular 5-millisecond intervals.&lt;br /&gt;
&lt;br /&gt;
The gameplay loop advances movement, animation, collision, combat, entity behavior, and other world systems according to the logical clock. The rendering loop draws the latest available world state according to the selected display rate. Since neither loop depends on the other to advance, changes in display performance, refresh rate, or VSync behavior do not ordinarily change the speed or timing of gameplay.&lt;br /&gt;
&lt;br /&gt;
=== Logical Time vs. FPS ===&lt;br /&gt;
&lt;br /&gt;
Logical time controls how frequently OpenBOR updates the simulated game world. Frames per second, or FPS, control how frequently the display loop renders that world. These are separate rates serving different purposes.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s logical clock runs at 200 ticks per second by default. Each logical tick therefore represents 5 milliseconds. The display loop, running at 60 FPS, renders once every 16.67 milliseconds. Since the two loops are decoupled, they do not need to align one-to-one. Under these default rates, OpenBOR ordinarily processes three or four logical ticks between displayed frames.&lt;br /&gt;
&lt;br /&gt;
Gameplay durations remain based on logical time regardless of how often the world is drawn. Running the display loop at 60, 200, 500, VSync, or an unrestricted rate does not reinterpret an animation delay, effect duration, movement timer, or other logical-time value.&lt;br /&gt;
&lt;br /&gt;
OpenBOR also provides an optional frame-aligned timing mode for projects that use traditional frame-count conventions. Creators may configure the logical clock to a rate evenly divisible by the desired frame rate and express animation delays as direct logical ticks. For example, a 180 Hz logical clock provides exactly three logical ticks for every interval of a 60 Hz timeline:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;delay 3 direct&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This represents one 60 Hz frame interval. Values of &amp;lt;code&amp;gt;6 direct&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;9 direct&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;12 direct&amp;lt;/code&amp;gt; represent two, three, and four frame intervals respectively. Frame-based timing can therefore be expressed exactly without coupling gameplay simulation to display rendering.&lt;br /&gt;
&lt;br /&gt;
See [[Animation Overview#Logical clock precision|Logical clock precision]] for more information about animation timing and direct delays.&lt;br /&gt;
&lt;br /&gt;
OpenBOR exposes the following primary measurements of time:&lt;br /&gt;
&lt;br /&gt;
=== Game Time ===&lt;br /&gt;
&lt;br /&gt;
Game Time is the traditional beat ’em up countdown used to maintain the pace of a level. It is a gameplay rule rather than the engine’s logical clock.&lt;br /&gt;
&lt;br /&gt;
When Game Time expires, players ordinarily lose a life. Creators may disable, remove, replace, or otherwise modify this behavior.&lt;br /&gt;
&lt;br /&gt;
See [[#Game Time|Game Time]].&lt;br /&gt;
&lt;br /&gt;
=== Elapsed Time ===&lt;br /&gt;
&lt;br /&gt;
Elapsed Time is the current level’s logical clock. It begins at `0` when active level gameplay starts and increments once for every processed logical tick.&lt;br /&gt;
&lt;br /&gt;
At the default 200 Hz logical rate, each increment represents 5 milliseconds. Animation timing, effect durations, entity simulation, and most other gameplay processes use Elapsed Time or values derived from it.&lt;br /&gt;
&lt;br /&gt;
See [[#Elapsed Time|Elapsed Time]].&lt;br /&gt;
&lt;br /&gt;
=== Ticks ===&lt;br /&gt;
&lt;br /&gt;
Ticks measure the number of milliseconds elapsed since OpenBOR started. Unlike Elapsed Time, Ticks are not limited to an active level and do not reset when a level begins.&lt;br /&gt;
&lt;br /&gt;
Ticks are useful for loading operations, menus, recorded input, diagnostics, and script timers that must remain available outside active gameplay.&lt;br /&gt;
&lt;br /&gt;
See [[#Ticks|Ticks]].&lt;br /&gt;
&lt;br /&gt;
=== System Time ===&lt;br /&gt;
&lt;br /&gt;
System Time provides optional access to the host device’s real-world clock, including Unix timestamps, calendar dates, local time, and Coordinated Universal Time.&lt;br /&gt;
&lt;br /&gt;
Native gameplay does not depend on System Time. Creators may use it through script for features such as dated save files, logging, calendar events, or other intentionally real-world behavior.&lt;br /&gt;
&lt;br /&gt;
See [[#System Time|System Time]].&lt;br /&gt;
== Game Time ==&lt;br /&gt;
Game time is a default countdown timer that appears at top center of the screen during an active level. It starts at 99 and decrements once every two real world seconds. At 0, all active players take damage equal to their current HP, and the game is over if no lives or credits remain. Time resets automatically when players clear a designated level wait and at the start of each new level. [[File:Batman savage dawn a.png|alt=Batman Savage Dawn screenshot.|center|frame|The Batman Savage Dawn fan game demonstrates OpenBOR&#039;s default game timer in action at top center of the screen.]]&lt;br /&gt;
&lt;br /&gt;
=== Set Time ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;settime {int time}&lt;br /&gt;
&lt;br /&gt;
#default&lt;br /&gt;
settime 99&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Level header command that sets value applied to timer on level start and native resets.&lt;br /&gt;
&lt;br /&gt;
* Accepts any integer value from &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to &amp;lt;code&amp;gt;99&amp;lt;/code&amp;gt;. Out of bounds values are ignored. &lt;br /&gt;
* &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; = infinite time.&lt;br /&gt;
&lt;br /&gt;
=== HUD Display ===&lt;br /&gt;
For configuring how timer displays on screen, see [[Heads Up Display]].&lt;br /&gt;
&lt;br /&gt;
* [[Heads Up Display#Disable Time Display|Disable Time Display]]&lt;br /&gt;
* [[Heads Up Display#Timer Font|Timer Font]]&lt;br /&gt;
* [[Heads Up Display#Timer Location and Border|Timer Location]]&lt;br /&gt;
* [[Heads Up Display#Timer Icon|Timer Icon]]&lt;br /&gt;
&lt;br /&gt;
=== Other ===&lt;br /&gt;
&lt;br /&gt;
==== data/sounds/timeover.wav ====&lt;br /&gt;
If available, plays once when the game timer expires.&lt;br /&gt;
&lt;br /&gt;
==== anim lose ====&lt;br /&gt;
If available, entity plays this animation in place of normal death when game timer expires. &lt;br /&gt;
&lt;br /&gt;
* Accessible to script as &amp;lt;code&amp;gt;openborconstant(&amp;quot;ANI_LOSE&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== anim falllose ====&lt;br /&gt;
If available and entity does not have a lose animation, entity plays this animation in place of normal fall death when game timer expires.&lt;br /&gt;
&lt;br /&gt;
* Accessible to script as &amp;lt;code&amp;gt;openborconstant(&amp;quot;ANI_FALLLOSE&amp;quot;)&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Attack types ====&lt;br /&gt;
Attack types applied to players when game time expires.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;openborconstant(&amp;quot;ATK_TIMEOVER&amp;quot;)&amp;lt;/code&amp;gt; - Applied to players without a lose animation.&lt;br /&gt;
* &amp;lt;code&amp;gt;openborconstant(&amp;quot;ATK_LOSE&amp;quot;)&amp;lt;/code&amp;gt; - Applied to players with a lose animation.&lt;br /&gt;
&lt;br /&gt;
=== Script API ===&lt;br /&gt;
&lt;br /&gt;
==== Game Time ====&lt;br /&gt;
Current game time is read and write accessible.&lt;br /&gt;
&lt;br /&gt;
===== Get =====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int time = openborvariant(&amp;quot;game_time&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Get integer representing game time remaining.&lt;br /&gt;
&lt;br /&gt;
===== Set =====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int new_time = 50;&lt;br /&gt;
&lt;br /&gt;
setopenborvariat(&amp;quot;game_time&amp;quot;, new_time);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Set game time remaining.&lt;br /&gt;
&lt;br /&gt;
==== Timetick Event ====&lt;br /&gt;
If available, &amp;lt;code&amp;gt;data/scripts/timetick.c&amp;lt;/code&amp;gt; fires on each game time decrement. Populates the following local variables.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;time&amp;lt;/code&amp;gt; - Current game time value.&lt;br /&gt;
&lt;br /&gt;
== Elapsed Time ==&lt;br /&gt;
In terms of game mechanics, Elapsed Time is OpenBOR’s most important and frequently used time measurement because it represents the engine’s logical clock. Its implementation, however, is relatively simple.&lt;br /&gt;
&lt;br /&gt;
At the beginning of each level, a global elapsed-time counter is initialized to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. OpenBOR’s logical clock runs at 200 ticks per second (200 Hz) by default, and the counter increments by &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; for every processed logical tick.&lt;br /&gt;
&lt;br /&gt;
At 200 Hz, each logical tick represents 5 milliseconds, or one-half of a centisecond. Therefore:&lt;br /&gt;
&lt;br /&gt;
* 2 elapsed-time ticks equal 1 centisecond.&lt;br /&gt;
* 200 elapsed-time ticks equal 1 second.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s logical clock operates independently of its outer update and rendering rate. Depending on the selected FPS setting and display synchronization, the outer update cycle may execute faster or slower than the logical clock. Available FPS settings include VSync, fixed limits of 60, 200, or 500 FPS, and an unrestricted option.&lt;br /&gt;
&lt;br /&gt;
Some operations execute once per outer update cycle. These include the global &amp;lt;code&amp;gt;update.c&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;updated.c&amp;lt;/code&amp;gt; script events. Such scripts may therefore execute at a different frequency than the logical clock.&lt;br /&gt;
&lt;br /&gt;
Other operations, including entity simulation and entity update scripts, execute according to logical ticks.&lt;br /&gt;
&lt;br /&gt;
Elapsed Time is therefore reliable as a gameplay timer regardless of the selected outer frame rate. Manually incremented script timers may produce inconsistent results when incremented from an event that executes once per outer update cycle. For timing operations outside active gameplay, where Elapsed Time is unavailable, consider using [[Time#Ticks|Ticks]] instead.&lt;br /&gt;
&lt;br /&gt;
=== Duration Testing ===&lt;br /&gt;
Most timed systems operate by setting an expiration value equal to the current Elapsed Time plus a predetermined duration. The engine then compares that expiration value against the elapsed-time counter. Once Elapsed Time reaches or passes the expiration value, the timed item may complete, expire, continue to another state, or perform whatever other action its system requires.&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
/* Set expiration. */&lt;br /&gt;
int expiration = elapsed_time + duration;&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* &amp;quot;Reach or exceed&amp;quot; logic downstream.&lt;br /&gt;
*/&lt;br /&gt;
if(elapsed_time &amp;gt;= expiration) {&lt;br /&gt;
    // Take action.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&#039;&#039;&#039;Note:&#039;&#039;&#039; In case it matters, the Elapsed Time counter is stored as a 64-bit unsigned integer. At 200 increments per second, a single level could run continuously for roughly 2.9 billion years before the counter rolls over.&lt;br /&gt;
&lt;br /&gt;
=== Script API ===&lt;br /&gt;
&lt;br /&gt;
==== Get Elapsed Time ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int elapsed_time = openborvariant(&amp;quot;elapsed_time&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Set Elapsed Time ====&lt;br /&gt;
Be careful about setting elapsed time during game-play, as again, virtually every active game timing property depends on it.&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int new_time = 100&lt;br /&gt;
&lt;br /&gt;
setopenborvariant(&amp;quot;elapsed_time&amp;quot;, new_time); &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Ticks ==&lt;br /&gt;
Ticks is a read only measure of milliseconds starting from game boot. OpenBOR natively uses ticks for the player input recording feature and for timing during load screens. Ticks therefore do not generally affect active game-play, but are useful for user content, scripting, and keeping time outside of active game-play elements when Elapsed Time is not natively incremented. &lt;br /&gt;
&lt;br /&gt;
=== Script API ===&lt;br /&gt;
&lt;br /&gt;
==== Get Ticks ====&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int ticks = openborvariant(&amp;quot;ticks&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== System Time ==&lt;br /&gt;
System time is entirely optional and provided for creator defined script use. It is not accessed by native engine logic in any way. System time is available as a Unix timestamp in either seconds or milliseconds where supported by hardware. Conversion functions allow outputting a custom formatted string or time parts as integer values for use in logic expressions.&lt;br /&gt;
&lt;br /&gt;
=== Script API ===&lt;br /&gt;
&lt;br /&gt;
==== Get Current Unix Timestamp ====&lt;br /&gt;
&lt;br /&gt;
Returns the current system time as the number of seconds elapsed since &amp;lt;code&amp;gt;1970-01-01 00:00:00 UTC&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int timestamp = datetime_gettimestamp();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Unix Timestamp in Milliseconds ====&lt;br /&gt;
&lt;br /&gt;
Returns the current system time as the number of milliseconds elapsed since &amp;lt;code&amp;gt;1970-01-01 00:00:00 UTC&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Millisecond Unix timestamps require a 64-bit integer value.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int timestamp = datetime_gettimestampms();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Format Current Local Date and Time ====&lt;br /&gt;
&lt;br /&gt;
Formats the current system time as a string using the host system&#039;s local time standard. The format string uses standard [https://pubs.opengroup.org/onlinepubs/007904875/utilities/date.html datetime conversion codes].&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Names such as weekdays and months may vary according to the host system&#039;s configured language and locale.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void date_time = datetime_format(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following examples demonstrate several common formats:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
/*&lt;br /&gt;
* ISO-style date and 24-hour time:&lt;br /&gt;
* 2026-07-12 14:35:08&lt;br /&gt;
*/&lt;br /&gt;
void date_time = datetime_format(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* Month, day, and year:&lt;br /&gt;
* 07/12/2026&lt;br /&gt;
*/&lt;br /&gt;
void date = datetime_format(&amp;quot;%m/%d/%Y&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* Full weekday and month names:&lt;br /&gt;
* Sunday, July 12, 2026&lt;br /&gt;
*/&lt;br /&gt;
void written_date = datetime_format(&amp;quot;%A, %B %d, %Y&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* Abbreviated weekday and month names:&lt;br /&gt;
* Sun, Jul 12, 2026&lt;br /&gt;
*/&lt;br /&gt;
void short_date = datetime_format(&amp;quot;%a, %b %d, %Y&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* 12-hour time with AM or PM:&lt;br /&gt;
* 02:35:08 PM&lt;br /&gt;
*/&lt;br /&gt;
void time_12_hour = datetime_format(&amp;quot;%I:%M:%S %p&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* 24-hour time:&lt;br /&gt;
* 14:35:08&lt;br /&gt;
*/&lt;br /&gt;
void time_24_hour = datetime_format(&amp;quot;%H:%M:%S&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* Year, month, day, hour, and minute:&lt;br /&gt;
* 20260712_1435&lt;br /&gt;
*&lt;br /&gt;
* Useful for save names, logs, and generated filenames.&lt;br /&gt;
*/&lt;br /&gt;
void file_timestamp = datetime_format(&amp;quot;%Y%m%d_%H%M&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* Day of the year:&lt;br /&gt;
* Day 193 of 2026&lt;br /&gt;
*/&lt;br /&gt;
void year_progress = datetime_format(&amp;quot;Day %j of %Y&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
/*&lt;br /&gt;
* Weekday name:&lt;br /&gt;
* Sunday&lt;br /&gt;
*/&lt;br /&gt;
void weekday = datetime_format(&amp;quot;%A&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Format a Unix Timestamp ====&lt;br /&gt;
&lt;br /&gt;
Formats a supplied Unix timestamp expressed in milliseconds.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int timestamp = 1783890000000;&lt;br /&gt;
&lt;br /&gt;
void date_time = datetime_format(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;, timestamp);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Format Date and Time as UTC ====&lt;br /&gt;
All format and date part functions can accept the following constants:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;opeborcosntat(&amp;quot;DATETIME_STANDARD_LOCAL&amp;quot;)&amp;lt;/code&amp;gt; - Use local system time (default).&lt;br /&gt;
* &amp;lt;code&amp;gt;opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;)&amp;lt;/code&amp;gt; - Use Coordinated Universal Time.&lt;br /&gt;
&lt;br /&gt;
Formats a supplied Unix timestamp using Coordinated Universal Time instead of the host system&#039;s local time.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int timestamp = datetime_gettimestampms();&lt;br /&gt;
&lt;br /&gt;
void date_time = datetime_format(&amp;quot;%Y-%m-%d %H:%M:%S&amp;quot;, timestamp, opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;)); &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Year ====&lt;br /&gt;
&lt;br /&gt;
Returns the current four-digit year using the host system&#039;s local time standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int year = datetime_getyear();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Month ====&lt;br /&gt;
&lt;br /&gt;
Returns the current calendar month as an integer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int month = datetime_getmonth();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Day ====&lt;br /&gt;
&lt;br /&gt;
Returns the current day of the month as an integer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int day = datetime_getday();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Weekday ====&lt;br /&gt;
Return the current day of the week as an integer corresponding to one of the following constants:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
openborconstant(&amp;quot;DATETIME_WEEKDAY_SUNDAY&amp;quot;)&lt;br /&gt;
openborconstant(&amp;quot;DATETIME_WEEKDAY_MONDAY&amp;quot;)&lt;br /&gt;
openborconstant(&amp;quot;DATETIME_WEEKDAY_TUESDAY&amp;quot;)&lt;br /&gt;
openborconstant(&amp;quot;DATETIME_WEEKDAY_WEDNESDAY&amp;quot;)&lt;br /&gt;
openborconstant(&amp;quot;DATETIME_WEEKDAY_THURSDAY&amp;quot;)&lt;br /&gt;
openborconstant(&amp;quot;DATETIME_WEEKDAY_FRIDAY&amp;quot;)&lt;br /&gt;
openborconstant(&amp;quot;DATETIME_WEEKDAY_SATURDAY&amp;quot;)&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int weekday = datetime_getweekday();&lt;br /&gt;
&lt;br /&gt;
/* Is it Monday? */&lt;br /&gt;
if(weekday == openborconstant(&amp;quot;DATETIME_WEEKDAY_MONDAY&amp;quot;)){&lt;br /&gt;
    log(&amp;quot;Just another Manic Monday.&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Hour ====&lt;br /&gt;
&lt;br /&gt;
Returns the current hour using the host system&#039;s local time standard.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int hour = datetime_gethour();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Minute ====&lt;br /&gt;
&lt;br /&gt;
Returns the current minute of the hour.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int minute = datetime_getminute();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Current Second ====&lt;br /&gt;
&lt;br /&gt;
Returns the current second of the minute.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int second = datetime_getsecond();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Date and Time Parts from a Unix Timestamp ====&lt;br /&gt;
&lt;br /&gt;
Each component function optionally accepts a Unix timestamp expressed in milliseconds. This allows creators to extract individual date and time values from a stored timestamp.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int timestamp = 1783890000000;&lt;br /&gt;
&lt;br /&gt;
int year   = datetime_getyear(timestamp);&lt;br /&gt;
int month  = datetime_getmonth(timestamp);&lt;br /&gt;
int day    = datetime_getday(timestamp);&lt;br /&gt;
int hour   = datetime_gethour(timestamp);&lt;br /&gt;
int minute = datetime_getminute(timestamp);&lt;br /&gt;
int second = datetime_getsecond(timestamp); &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Get Date and Time Parts as UTC ====&lt;br /&gt;
&lt;br /&gt;
Pass &amp;lt;code&amp;gt;opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;)&amp;lt;/code&amp;gt; as the second argument to interpret the supplied timestamp as Coordinated Universal Time.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int timestamp = datetime_gettimestampms();&lt;br /&gt;
&lt;br /&gt;
int year = datetime_getyear(timestamp, opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
int month = datetime_getmonth(timestamp, opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
int day = datetime_getday(timestamp, opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;));&lt;br /&gt;
&lt;br /&gt;
int hour = datetime_gethour(timestamp, opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;)); &amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Use Current Time with an Explicit Time Standard ====&lt;br /&gt;
&lt;br /&gt;
Pass `NULL()` for the optional timestamp argument when selecting a time standard without supplying a custom timestamp.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int utc_hour = datetime_gethour(NULL(), opeborcosntat(&amp;quot;DATETIME_STANDARD_UTC&amp;quot;));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
[[Category:Script]]&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Game Mechanics]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1098</id>
		<title>Animation Overview</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Animation_Overview&amp;diff=1098"/>
		<updated>2026-08-26T03:53:31Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Animations are one of OpenBOR&#039;s fundamental building blocks. Models use animations to describe how they look and behave over time. Walking, standing, attacking, falling, taking damage, and many other actions are represented by ordered animation frames.&lt;br /&gt;
&lt;br /&gt;
Each animation belongs to a model and occupies a recognized animation slot such as &amp;lt;code&amp;gt;idle&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;walk&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;attack1&amp;lt;/code&amp;gt;. OpenBOR&#039;s native logic selects many of these slots automatically. Scripts may also select an animation or frame directly.&lt;br /&gt;
&lt;br /&gt;
An animation frame is more than an image. It may also contain timing, offsets, collision boxes, movement, sounds, draw settings, entity spawns, and other behavior.__TOC__&lt;br /&gt;
&lt;br /&gt;
== Basic structure ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; command begins an animation definition. Commands following it configure the animation or prepare properties for its frames. Each &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command adds one frame using the properties currently in effect.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 12&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This example creates a looping idle animation with three frames. With the default centisecond delay unit, each frame lasts 0.12 seconds.&lt;br /&gt;
&lt;br /&gt;
Indentation and blank lines are optional, but they make animation blocks easier to read.&lt;br /&gt;
&lt;br /&gt;
=== Animation identifiers ===&lt;br /&gt;
&lt;br /&gt;
The value following &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt; identifies the animation&#039;s purpose. Common examples include:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Identifier&lt;br /&gt;
! Typical purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;idle&amp;lt;/code&amp;gt;&lt;br /&gt;
| Standing without moving.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;walk&amp;lt;/code&amp;gt;&lt;br /&gt;
| Ordinary movement.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;jump&amp;lt;/code&amp;gt;&lt;br /&gt;
| Initial or rising jump state.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;land&amp;lt;/code&amp;gt;&lt;br /&gt;
| Landing from a jump or fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;pain&amp;lt;/code&amp;gt;&lt;br /&gt;
| Reacting to damage.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;fall&amp;lt;/code&amp;gt;&lt;br /&gt;
| Knockdown or falling reaction.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;rise&amp;lt;/code&amp;gt;&lt;br /&gt;
| Recovering from a fall.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;attack1&amp;lt;/code&amp;gt;&lt;br /&gt;
| A native attack animation.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;freespecial1&amp;lt;/code&amp;gt;&lt;br /&gt;
| A creator-configured special move.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Animation identifiers are behavioral slots, not arbitrary labels. Supplying an animation makes the corresponding behavior available to native engine logic and scripts. Some model types require certain animations to operate normally, while others are optional.&lt;br /&gt;
&lt;br /&gt;
== Frame construction ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR reads model files from top to bottom. Frame property commands update temporary state, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; commits a copy of that state to the animation.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    delay 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 25&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame receives a delay of 10. The second receives a delay of 25 because the delay changes before its &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
Commands placed after a &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line cannot alter the frame that was already added. Most frame properties remain active for subsequent frames until another command changes or disables them. Some one-shot properties, such as a frame sound, reset after the frame is added.&lt;br /&gt;
&lt;br /&gt;
Define important state explicitly in each animation. In particular, specifying a delay inside every animation avoids accidentally inheriting a previously active delay while the model file is being parsed.&lt;br /&gt;
=== Frame numbering ===&lt;br /&gt;
Frames use zero-based indexes. The first frame line is frame 0, the second is frame 1, and so on. Commands that target a frame number use these indexes unless their own documentation states otherwise.&lt;br /&gt;
&lt;br /&gt;
The same image may appear more than once when different timing or behavior is needed. OpenBOR reuses its globally cached pixel data, so repeating an image does not require another pixel-data allocation.&lt;br /&gt;
&lt;br /&gt;
Frame &amp;lt;code&amp;gt;none&amp;lt;/code&amp;gt; adds a logical frame without a visible sprite, which can be useful for invisible states or script-controlled behavior.&lt;br /&gt;
&lt;br /&gt;
== Offsets ==&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt; command selects the anchor point inside a frame image.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
offset 32 96&lt;br /&gt;
frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR places the selected point at the entity&#039;s world position. For a standing character, the anchor is usually near the ground between the character&#039;s feet.&lt;br /&gt;
&lt;br /&gt;
Choose a consistent animation canvas size during production and align every source frame to the same anchor before export. Guides for the ground line and horizontal center make this much faster than correcting alignment one frame at a time in the model file.&lt;br /&gt;
&lt;br /&gt;
OpenBOR automatically trims unused image space when it loads sprites. Consistent source canvases are still useful because the trimming process preserves the intended relationship between the artwork and its anchor without retaining transparent padding in memory.&lt;br /&gt;
&lt;br /&gt;
Poor source alignment makes an entity appear to shake, slide, or change height when frames cycle. Per-frame offset changes remain available for deliberate repositioning or assets that cannot be realigned, but they should generally be the exception rather than the primary production workflow.&lt;br /&gt;
&lt;br /&gt;
== Looping ==&lt;br /&gt;
Animations do not loop unless looping is enabled.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
loop {enable} {loop frame - optional} {end frame - optional}&lt;br /&gt;
&lt;br /&gt;
# Default&lt;br /&gt;
loop 0 0 0&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;loop &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; repeats the animation. loop &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; disables looping.&lt;br /&gt;
&lt;br /&gt;
The optional loop frame selects the zero-based frame to return to when the animation repeats. The optional end frame is an exclusive boundary - when playback reaches that frame index, it returns to the selected loop frame instead of displaying the boundary frame. &lt;br /&gt;
&lt;br /&gt;
For example, if end frame is &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt;, frame &amp;lt;code&amp;gt;7&amp;lt;/code&amp;gt; - the sixth frame, does not display - the animation instantly loops on reaching that frame. If end frame is not provided animation proceeds to the last frame and loops.&lt;br /&gt;
&lt;br /&gt;
Idle and walk animations commonly loop. Attacks, pain reactions, falls, and transitions commonly play once and stop on their final frame until native logic or a script selects another animation.&lt;br /&gt;
&lt;br /&gt;
== Delay ==&lt;br /&gt;
&lt;br /&gt;
Delay controls how long a frame remains active before OpenBOR advances the animation. The command is stateful and applies to each following frame until another &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt; command changes it.&lt;br /&gt;
&lt;br /&gt;
=== Syntax ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay &amp;lt;value&amp;gt; [unit]&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The unit is optional. Omitting it is equivalent to selecting &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 10&lt;br /&gt;
delay 10 global&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Both examples use the module&#039;s global delay unit.&lt;br /&gt;
&lt;br /&gt;
=== Delay units ===&lt;br /&gt;
Optional time unit setting. &lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Unit&lt;br /&gt;
! Meaning&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;&lt;br /&gt;
| Uses the unit selected by &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt;. This is the default mode.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as thousandths of a second.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as hundredths of a second. This is the default global unit and preserves traditional OpenBOR timing.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as seconds.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;&lt;br /&gt;
| Interprets the value as minutes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;&lt;br /&gt;
| Stores the value directly as logical clock ticks without time-unit conversion.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
OpenBOR converts the supplied value from the selected unit into logical clock ticks. See [[Animation Overview#Logical clock precision|Logical clock precision]].&lt;br /&gt;
&lt;br /&gt;
Examples:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay 250 millisecond&lt;br /&gt;
delay 25 centisecond&lt;br /&gt;
delay 2 second&lt;br /&gt;
delay 1 minute&lt;br /&gt;
delay 50 direct&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Singular unit names are required.&lt;br /&gt;
&lt;br /&gt;
=== Global delay unit ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; in &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt; to select the unit used by unqualified and explicitly global delays.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
global_config_delay_unit centisecond&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The setting accepts &amp;lt;code&amp;gt;millisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;second&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;minute&amp;lt;/code&amp;gt;, or &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt;. It does not accept &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, because the global setting must resolve to a concrete unit.&lt;br /&gt;
&lt;br /&gt;
If the command is omitted, the global unit is &amp;lt;code&amp;gt;centisecond&amp;lt;/code&amp;gt;. Changing the setting reinterprets every model delay that uses &amp;lt;code&amp;gt;global&amp;lt;/code&amp;gt;, so modules should choose a unit deliberately. Explicit frame units remain independent of the global selection.&lt;br /&gt;
&lt;br /&gt;
The setting is read while models are loaded. OpenBOR converts each delay to logical ticks and stores the converted result with the animation frame. It is not a runtime timing switch for animations that are already loaded.&lt;br /&gt;
&lt;br /&gt;
=== Logical clock precision ===&lt;br /&gt;
&lt;br /&gt;
OpenBOR animation timing ultimately uses whole logical clock ticks. Unit conversion follows these relationships:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
millisecond = value * game speed / 1000&lt;br /&gt;
centisecond = value * game speed / 100&lt;br /&gt;
second      = value * game speed&lt;br /&gt;
minute      = value * game speed * 60&lt;br /&gt;
direct      = value&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Fractional results truncate. No time unit can represent a duration smaller than the module&#039;s logical clock precision, and any fractional remainder is discarded.&lt;br /&gt;
&lt;br /&gt;
For example, at the default game speed of 200 ticks per second, each logical tick represents 5 milliseconds. Any value from 15 through 19 milliseconds therefore converts to three ticks, representing 15 milliseconds, while 20 milliseconds converts exactly to four ticks.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;direct&amp;lt;/code&amp;gt; unit is useful when exact integration with the logical clock matters. Its real-time duration changes with game speed because its value is already a tick count. By pairing a game speed evenly divisible by a target frame rate with direct delays, creators can time animations in exact frame intervals. For example, a game speed of 240 provides four logical ticks for each frame at 60 Hz. This supports frame-based timing conventions commonly used by the fighting game community and fixed-refresh console games.&lt;br /&gt;
&lt;br /&gt;
=== Infinite delay ===&lt;br /&gt;
&lt;br /&gt;
Use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or the infinity symbol &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; when a frame should not advance through ordinary clock timing.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    delay infinite&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
delay ∞&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
An infinite frame remains active until something outside normal timing changes its animation or frame. Scripts may use the &amp;lt;code&amp;gt;DELAY_INFINITE&amp;lt;/code&amp;gt; constant for the same engine value.&lt;br /&gt;
&lt;br /&gt;
The word &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; is the most portable model-file spelling. The &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; form is UTF-8 text and may be damaged by old Windows editors, archive tools, or other utilities that do not preserve Unicode correctly.&lt;br /&gt;
&lt;br /&gt;
Any negative numeric delay also becomes infinite for backward compatibility. New content should use &amp;lt;code&amp;gt;infinite&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;∞&amp;lt;/code&amp;gt; so its intent is explicit.&lt;br /&gt;
&lt;br /&gt;
=== Finite range and behavior flags ===&lt;br /&gt;
&lt;br /&gt;
Finite delay values use the lower 32 bits of an unsigned 64-bit value. The largest finite value is 4,294,967,295. Conversions that would exceed this limit clamp to the largest finite delay instead of overflowing or accidentally becoming infinite.&lt;br /&gt;
&lt;br /&gt;
The upper 32 bits are reserved for delay behavior flags. Bit 63 is the infinite flag. Enhanced-delay calculations modify only the finite lower value and preserve behavior flags, so an infinite frame cannot accidentally become finite.&lt;br /&gt;
&lt;br /&gt;
=== Enhanced delay ===&lt;br /&gt;
&lt;br /&gt;
[[Enhanced Delay|Enhanced delay]], or &amp;lt;code&amp;gt;edelay&amp;lt;/code&amp;gt;, adjusts frame delays through a model&#039;s multiplier, modifier, range, and cap settings. These calculations occur after time-unit conversion, so enhanced-delay ranges, modifiers, and caps operate on logical tick values rather than the source unit written in the animation.&lt;br /&gt;
&lt;br /&gt;
Enhanced-delay arithmetic is bounded to the finite delay range. It cannot overflow into behavior flags or create an infinite delay through calculation.&lt;br /&gt;
&lt;br /&gt;
== Frame properties ==&lt;br /&gt;
&lt;br /&gt;
Timing and graphics form only the minimum animation. Frame property commands can add behavior such as:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Property&lt;br /&gt;
! Purpose&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;bbox&amp;lt;/code&amp;gt;&lt;br /&gt;
| Defines the frame&#039;s body collision area. Use &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; to disable it.&lt;br /&gt;
|-&lt;br /&gt;
| Attack collision commands&lt;br /&gt;
| Define areas that damage or otherwise affect targets.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;move&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;movea&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;movez&amp;lt;/code&amp;gt;&lt;br /&gt;
| Apply frame movement along the model&#039;s axes.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;sound&amp;lt;/code&amp;gt;&lt;br /&gt;
| Plays a sound when the frame begins.&lt;br /&gt;
|-&lt;br /&gt;
| Draw commands&lt;br /&gt;
| Change scaling, rotation, transparency, remapping, clipping, and other rendering behavior.&lt;br /&gt;
|-&lt;br /&gt;
| Spawn commands&lt;br /&gt;
| Create or summon child entities on selected frames.&lt;br /&gt;
|-&lt;br /&gt;
| Animation scripts&lt;br /&gt;
| Execute creator-defined logic as frames change.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Property commands normally appear before the &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; they configure:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    delay 10&lt;br /&gt;
    offset 32 96&lt;br /&gt;
&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    bbox none&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The first frame has a body collision box. The second does not because &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; changes the pending frame state before the second &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; command.&lt;br /&gt;
&lt;br /&gt;
== Practical example ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
anim idle&lt;br /&gt;
    loop 1&lt;br /&gt;
    offset 32 96&lt;br /&gt;
    bbox 12 20 40 76 10&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_01.png&lt;br /&gt;
&lt;br /&gt;
    delay 150 millisecond&lt;br /&gt;
    frame data/chars/example/idle_02.png&lt;br /&gt;
&lt;br /&gt;
    delay 100 millisecond&lt;br /&gt;
    frame data/chars/example/idle_03.png&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
anim attack1&lt;br /&gt;
    loop 0&lt;br /&gt;
    offset 40 96&lt;br /&gt;
    delay 80 millisecond&lt;br /&gt;
&lt;br /&gt;
    frame data/chars/example/attack_01.png&lt;br /&gt;
&lt;br /&gt;
    move 4&lt;br /&gt;
    frame data/chars/example/attack_02.png&lt;br /&gt;
&lt;br /&gt;
    move 0&lt;br /&gt;
    delay 160 millisecond&lt;br /&gt;
    frame data/chars/example/attack_03.png&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The idle animation loops with intentionally uneven timing. The attack plays once, moves the entity during its second frame, stops that movement before the third frame, and holds the final attack pose longer.&lt;br /&gt;
&lt;br /&gt;
== Common problems ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Symptom&lt;br /&gt;
! Likely cause&lt;br /&gt;
|-&lt;br /&gt;
| Frames appear to shake or slide.&lt;br /&gt;
| Offsets do not align the same visual anchor across images.&lt;br /&gt;
|-&lt;br /&gt;
| A property affects the wrong frame.&lt;br /&gt;
| The property command appears after the intended &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt; line, or persistent frame state was not changed before the next frame.&lt;br /&gt;
|-&lt;br /&gt;
| An animation plays much too quickly or slowly.&lt;br /&gt;
| The delay unit or &amp;lt;code&amp;gt;global_config_delay_unit&amp;lt;/code&amp;gt; does not match the values used by the model.&lt;br /&gt;
|-&lt;br /&gt;
| Very small delays appear identical.&lt;br /&gt;
| Both values truncate to the same logical tick count at the selected game speed.&lt;br /&gt;
|-&lt;br /&gt;
| An animation never advances.&lt;br /&gt;
| Its delay is infinite, its animation has stopped, or external logic is holding or replacing its state.&lt;br /&gt;
|-&lt;br /&gt;
| A non-looping animation does not repeat.&lt;br /&gt;
| This is expected unless native logic or a script selects the animation again.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
When debugging an animation, begin with a minimal definition containing &amp;lt;code&amp;gt;anim&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;delay&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;offset&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;frame&amp;lt;/code&amp;gt;. Confirm its images, alignment, and timing before adding collision, movement, effects, or scripts.&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Animation]]&lt;br /&gt;
[[Category:Model]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1097</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1097"/>
		<updated>2026-08-26T03:49:26Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus means we spend our development resources on performance and depth: butter-smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from plugins.&lt;br /&gt;
&lt;br /&gt;
Then we layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting. Set an area&#039;s Z axis to bounded values and the engine natively assumes 2D world behaviors.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and seamless motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1096</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1096"/>
		<updated>2026-08-26T03:45:04Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus means we spend our development resources on performance and depth: butter-smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from plugins.&lt;br /&gt;
&lt;br /&gt;
Then we layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting. Set an area&#039;s Z axis to a bounded value and the engine natively assumes 2D world behaviors.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and seamless motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1095</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1095"/>
		<updated>2026-08-25T17:39:31Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus means we spend our development resources on quality and depth: butter-smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from third-party plugins.&lt;br /&gt;
&lt;br /&gt;
Then we layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting. Set an area&#039;s Z axis to a bounded value and the engine natively assumes 2D world behaviors.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and seamless motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1094</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1094"/>
		<updated>2026-08-25T17:38:05Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus means we spend our development resources on quality and depth: butter-smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from third-party plugins.&lt;br /&gt;
&lt;br /&gt;
Then we layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting. Set an area&#039;s Z axis to a bounded value and the engine natively assumes 2D world behaviors.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1093</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1093"/>
		<updated>2026-08-25T17:34:52Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus means we spend our development resources on quality and depth: butter-smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from third-party plugins.&lt;br /&gt;
&lt;br /&gt;
Then we layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting. Set a bounded Z axis and the engine natively assumes 2D world behaviors.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1092</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1092"/>
		<updated>2026-08-25T17:30:48Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus means we spend our development resources on quality and depth: butter-smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from third-party plugins.&lt;br /&gt;
&lt;br /&gt;
Then we layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1091</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1091"/>
		<updated>2026-08-25T17:28:46Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus means we spend our development resources on quality and depth: butter-smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from third-party plugins.&lt;br /&gt;
&lt;br /&gt;
We layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1090</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1090"/>
		<updated>2026-08-25T17:23:32Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus lets us invest our development resources on quality and depth. Butter smooth motion, asset handling, controls, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from third-party plugins.&lt;br /&gt;
&lt;br /&gt;
We layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1089</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1089"/>
		<updated>2026-08-25T17:21:14Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular result: role-playing games, one-on-one fighters, shoot-’em-ups, real-time strategy, or puzzles. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by specializing in a technical medium without prescribing the result.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately limits itself to the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite-powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus lets us invest our development resources in depth. Asset handling, controls, movement, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are integrated and ready to use. Everything works together out of the box, with no need to build these foundations or assemble them from third-party plugins.&lt;br /&gt;
&lt;br /&gt;
We layer beat ’em up-oriented behavior over those systems as the default starting point. Supply the necessary assets and configuration, and characters can already move, fight, react, navigate depth, cooperate, and progress through levels. You can have a functional game running within hours, but those defaults don&#039;t dictate the finished product.&lt;br /&gt;
&lt;br /&gt;
Configuration and script expose the same engine objects, properties, and low-level primitives beneath that native behavior. Creators can inspect runtime state, customize selected operations, replace complete subsystems, or combine the primitives into something we never anticipated. The same technical model can support platformers, shooters, role-playing games, puzzles, and other designs far removed from the default.&lt;br /&gt;
&lt;br /&gt;
The result is a deliberately bounded medium, a complete starting point, and an ending defined only by your imagination.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1088</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1088"/>
		<updated>2026-08-25T17:13:11Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular genre. Role-playing games. One-on-one fighters. Shoot-’em-ups. Real-time strategy. Puzzle games. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by choosing a technical lane rather than a result lane.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately stays within the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Within that foundation, virtually any sprite powered concept is possible.&lt;br /&gt;
&lt;br /&gt;
That focus lets us concentrate our development resources on making OpenBOR the most capable, efficient, and complete engine possible for its chosen medium. Asset handling, controls, movement, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are fully integrated and ready to use. Creators do not need to build these foundations or assemble them from third-party plugins before they can begin making a game.&lt;br /&gt;
&lt;br /&gt;
We then layer beat ’em up-oriented behavior over those systems as the default starting point. Characters can move, fight, react, navigate depth, cooperate, and progress through levels as soon as creators supply the necessary assets and configuration. Those defaults can put a functional game on screen within hours, though they do not dictate what the finished game must become.&lt;br /&gt;
&lt;br /&gt;
The same engine objects, properties, and low-level primitives remain exposed through configuration and script. Creators can inspect runtime state, customize selected behavior, replace complete subsystems, or combine the primitives into something we never anticipated. Platformers, shooters, role-playing games, puzzle games, and other designs can all operate within the same technical model.&lt;br /&gt;
&lt;br /&gt;
Staying within a technical lane therefore allows us to make OpenBOR more complete without making it less versatile. Native functionality provides a high starting point, not a boundary. We are not trying to make an engine that does everything. We are working to make the most complete, efficient, and open-ended engine possible for the medium we have chosen.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1087</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1087"/>
		<updated>2026-08-25T17:10:36Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how we define its scope. Some engines concentrate on a particular genre. Role-playing games. One-on-one fighters. Shoot-’em-ups. Real-time strategy. Puzzle games. Others attempt to support every technical medium, leaving developers to build most game-specific functionality themselves. Both are valid approaches. We take a third path by choosing a technical lane rather than a result lane.&lt;br /&gt;
&lt;br /&gt;
OpenBOR deliberately stays within the simulation and presentation of flat, two-dimensional visual assets in an effectively limitless three-dimensional world. No polygons. No meshes. No skeletal rigs. Just raster graphics moving through three axes. Those choices limit how an OpenBOR world is represented, not what creators can produce within it.&lt;br /&gt;
&lt;br /&gt;
That focus lets us concentrate our development resources on making OpenBOR the most capable, efficient, and complete engine possible for its chosen medium. Asset handling, controls, movement, gravity, collision, combat, blocking, grabbing, throwing, artificial intelligence, cameras, level trees, audio, menus, HUDs, saving, and local multiplayer are fully integrated and ready to use. Creators do not need to build these foundations or assemble them from third-party plugins before they can begin making a game.&lt;br /&gt;
&lt;br /&gt;
We then layer beat ’em up-oriented behavior over those systems as the default starting point. Characters can move, fight, react, navigate depth, cooperate, and progress through levels as soon as creators supply the necessary assets and configuration. Those defaults can put a functional game on screen within hours, though they do not dictate what the finished game must become.&lt;br /&gt;
&lt;br /&gt;
The same engine objects, properties, and low-level primitives remain exposed through configuration and script. Creators can inspect runtime state, customize selected behavior, replace complete subsystems, or combine the primitives into something we never anticipated. Platformers, shooters, role-playing games, puzzle games, and other designs can all operate within the same technical model.&lt;br /&gt;
&lt;br /&gt;
Staying within a technical lane therefore allows us to make OpenBOR more complete without making it less versatile. Native functionality provides a high starting point, not a boundary. We are not trying to make an engine that does everything. We are working to make the most complete, efficient, and open-ended engine possible for the medium we have chosen.&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Collision&amp;diff=1086</id>
		<title>Collision</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Collision&amp;diff=1086"/>
		<updated>2026-08-25T16:32:16Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Multiple Boxes */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Collision boxes tell OpenBOR (or any other game engine) what portions of an entity are able to hit or get hit and what areas of a level are walls or platforms. In OpenBOR these boxes are geometric cubes the engine continuously compares to each other – whenever two or more cubes overlap, that’s a collision and the engine will react accordingly.&lt;br /&gt;
&lt;br /&gt;
Depending on the type of collision box, it will have other properties that tell the engine how to handle the collision event. This article focuses on Attack (hitting with attacks) and Body (Receiving attacks and taking up space). Walls and platforms work on a similar principal and are covered in level design.&lt;br /&gt;
&lt;br /&gt;
== Multiple Boxes ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR supports multiple collision boxes per frame, up to 64 of each type. To define a second box of any type, add &amp;lt;code&amp;gt;collision.index {index}&amp;lt;/code&amp;gt; with your desired index and then define a set of properties for the next box. You may use any integer value from &amp;lt;code&amp;gt;0 - 63&amp;lt;/code&amp;gt; for &amp;lt;code&amp;gt;{index}&amp;lt;/code&amp;gt;. All subsequent collision box properties below &amp;lt;code&amp;gt;collision.index {index}&amp;lt;/code&amp;gt; now act on the new index.&lt;br /&gt;
&lt;br /&gt;
You can repeat this process to add a third box, a fourth box, and so on. To adjust or disable the boxes, again use &amp;lt;code&amp;gt;collision.index {index}&amp;lt;/code&amp;gt; to define which box you wish to act on, then disable or adjust the box properties as you would a single occurrence.&lt;br /&gt;
&lt;br /&gt;
When two or more attack boxes overlap a body box or body boxes, OpenBOR will acknowledge a single collision with the lowest index attack and body boxes taking priority.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; Don’t get carried away with multiple boxes. Collision boxes are meant to describe practical contact areas, not trace literal sprite pixels. Even high precision versus fighters like &#039;&#039;Street Fighter&#039;&#039; and &#039;&#039;King of Fighters&#039;&#039; typically use only two or three body boxes and one attack box. Beat em’ ups and action games almost invariably use only one of each outside of edge cases.&lt;br /&gt;
&lt;br /&gt;
Take the following two examples. The first is an unfortunately real amateur design with impressive-looking but needlessly wasteful boxes. This is not precise collision – it is a pointless flex. In game, it will likely feel janky and inconsistent.&lt;br /&gt;
[[File:Collision bad.webp|none|frame|Eight times the work - half the precision.]]&lt;br /&gt;
Now observe this design – a professional example straight from Capcom’s &#039;&#039;Capcom vs. SNK&#039;&#039; series. It uses only the minimum needed: broad, readable body coverage and a clear attack area. That is not just more optimal – it is mechanically stronger in play.&lt;br /&gt;
[[File:Collision cvs a.webp|none|frame|The pros know.]]&lt;br /&gt;
&lt;br /&gt;
== Attack ==&lt;br /&gt;
&lt;br /&gt;
Attacks hit and presumably cause damage to other entities. To define an attack, add the necessary attack commands from list below immediately before the frame. In most cases you only need the basic dimensions and damage value. The other attack properties are optional and most are disabled by default.&lt;br /&gt;
&lt;br /&gt;
Once defined, an attack remains active throughout remainder of the animation unless you redefine it. To disable an attack, add &amp;lt;code&amp;gt;attack 0&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;attack none&amp;lt;/code&amp;gt; before a frame.&lt;br /&gt;
&lt;br /&gt;
=== Command List ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Command&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| attack.block.cost&lt;br /&gt;
| 0&lt;br /&gt;
| Amount attack removes from target’s &#039;&#039;guardpoints&#039;&#039; when blocked.&lt;br /&gt;
|-&lt;br /&gt;
| attack.block.penetrate&lt;br /&gt;
| 0&lt;br /&gt;
| If this meets or exceeds target’s block threshold, target cannot block attack.&lt;br /&gt;
|-&lt;br /&gt;
| attack.counter&lt;br /&gt;
| 0&lt;br /&gt;
| If 1+, this attack treats target’s attack boxes as body boxes. If both attacks have counter value, highest value has priority.&lt;br /&gt;
|-&lt;br /&gt;
|attack.coordinates&lt;br /&gt;
|0 0 0 0 0 0&lt;br /&gt;
|Shortcut for setting all basic coordinates in a single line: &amp;lt;code&amp;gt;x, y, width, height, depth background, depth foreground&amp;lt;/code&amp;gt;. &lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; All &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; disables the collision box. If any other values are defined and both z depth values are &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; or undefined, they will be replaced with a default value of &amp;lt;code&amp;gt;&#039;&#039;grabdistance&#039;&#039; setting / 3 + 1&amp;lt;/code&amp;gt;. Assuming the [[Grabdistance]] default of &amp;lt;code&amp;gt;36&amp;lt;/code&amp;gt;, this gives the box a depth of 13 pixels in each direction.&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.force&lt;br /&gt;
| 0&lt;br /&gt;
| Value deducted from target’s hit points.&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.land.force&lt;br /&gt;
| 0&lt;br /&gt;
| If attack causes a fall, value deducted from target’s hit points on landing.&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.land.mode&lt;br /&gt;
| 0&lt;br /&gt;
| If attack causes knockdown, target’s projectile status and landing ability. Projectile status has no effect if target’s fall animations do not have an attack box.&lt;br /&gt;
* &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; – Normal fall. Target’s attack boxes are disabled until landing.&lt;br /&gt;
* &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; – Target’s attack boxes are enabled in projectile mode until landing, meaning target will hit its allies.&lt;br /&gt;
* &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; – Same as 1. Additionally, if player controlled targets have LAND animation available, they may play it on landing and nullify land force by holding Up + Jump.&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.lethal.disable&lt;br /&gt;
| 0&lt;br /&gt;
| If true, attack cannot reduce target’s hit points below 1.&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.recursive.force&lt;br /&gt;
| 0&lt;br /&gt;
| Amount deducted from target’s hitpoints on each recursive tick. See [https://chronocrash.com/obor/wiki/recursive-damage/ Recursive Damage].&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.recursive.index&lt;br /&gt;
| 0&lt;br /&gt;
| Index ID applied to recursive damage. See [https://chronocrash.com/obor/wiki/recursive-damage/ Recursive Damage].&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.recursive.mode&lt;br /&gt;
| 0&lt;br /&gt;
| How recursive damage applies to target’s hit points or magic points. See [https://chronocrash.com/obor/wiki/recursive-damage/ Recursive Damage].&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.recursive.tag&lt;br /&gt;
| 0&lt;br /&gt;
| Creator defined value ignored by native engine logic. See [https://chronocrash.com/obor/wiki/recursive-damage/ Recursive Damage].&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.recursive.time&lt;br /&gt;
| 0&lt;br /&gt;
| How long in [[Time#Elapsed Time 2|elapsed time]] the recursive damage effect will last. See [https://chronocrash.com/obor/wiki/recursive-damage/ Recursive Damage].&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.recursive.rate&lt;br /&gt;
| 0&lt;br /&gt;
| How long in [[Time#Elapsed Time 2|elapsed time]] between each tick of recursive damage. See [https://chronocrash.com/obor/wiki/recursive-damage/ Recursive Damage].&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.steal&lt;br /&gt;
| 0&lt;br /&gt;
| If true, &amp;lt;code&amp;gt; attack.damage.force &amp;lt;/code&amp;gt; is added to attacking entity’s hit points.&lt;br /&gt;
|-&lt;br /&gt;
| attack.damage.type&lt;br /&gt;
| 1&lt;br /&gt;
| Attacking type. Works with target’s defense values and reacting animations. Accepts following values:&lt;br /&gt;
* Any &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;+ number. For legacy compatibility, attacks are NOT 0 indexed. If you provide a value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; is used instead.&lt;br /&gt;
* &amp;lt;code&amp;gt;Blast&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Burn&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Freeze&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Shock&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Steal&amp;lt;/code&amp;gt;&lt;br /&gt;
See [[Attack Type|Attack Types]] for details.&lt;br /&gt;
|-&lt;br /&gt;
| attack.effect.block.flash.model&lt;br /&gt;
| None&lt;br /&gt;
| Name of model to spawn when target blocks attack.&lt;br /&gt;
|-&lt;br /&gt;
| attack.effect.block.sound.path&lt;br /&gt;
| None&lt;br /&gt;
| Path to sound file that plays when target blocks attack.&lt;br /&gt;
|-&lt;br /&gt;
| attack.effect.hit.flash.layer.adjust&lt;br /&gt;
| 0&lt;br /&gt;
| Added to global layer adjustment of flash spawn drawing layer. See [[Hit Effects]].&lt;br /&gt;
|-&lt;br /&gt;
| attack.effect.hit.flash.layer.source&lt;br /&gt;
| 0&lt;br /&gt;
| Added to global hit flash layer source value for controlling source of flash spawn drawing layer. See [[Hit Effects]].&lt;br /&gt;
|-&lt;br /&gt;
| attack.effect.hit.flash.model&lt;br /&gt;
| None&lt;br /&gt;
| Name of model to spawn when attack hits target. If undefined, the global flash model spawns.&lt;br /&gt;
|-&lt;br /&gt;
| attack.effect.hit.flash.disable&lt;br /&gt;
| 0&lt;br /&gt;
| If true, no model spawns when attack hits target.&lt;br /&gt;
|-&lt;br /&gt;
| attack.effect.hit.sound.path&lt;br /&gt;
| None&lt;br /&gt;
| Path to sound file that plays when attack hits target.&lt;br /&gt;
|-&lt;br /&gt;
| attack.ground.mode&lt;br /&gt;
| 0&lt;br /&gt;
| Ability to hit fallen targets on the ground.&lt;br /&gt;
* &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; - Attack cannot hit lying targets.&lt;br /&gt;
* &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; - Attack can hit lying targets.&lt;br /&gt;
* &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; - Attack only hits lying targets.&lt;br /&gt;
|-&lt;br /&gt;
| collision.index&lt;br /&gt;
| 0&lt;br /&gt;
| Used for multiple attack boxes on one animation frame. 0 is the first attack box. 1 is the second, and so on. Place this immediately before other properties to have them act on that index. See &#039;&#039;multiple collision boxes&#039;&#039; for details.&lt;br /&gt;
|-&lt;br /&gt;
| attack.map.index&lt;br /&gt;
| 0&lt;br /&gt;
|&lt;br /&gt;
* If &amp;lt;code&amp;gt;1+&amp;lt;/code&amp;gt;, target entity switches to this palette index. For example, if hit by &amp;lt;code&amp;gt;attack.map.index 3&amp;lt;/code&amp;gt;, the target switches to palette &amp;lt;code&amp;gt;3&amp;lt;/code&amp;gt; if available.&lt;br /&gt;
* If &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;, target applies its freeze palette.&lt;br /&gt;
&lt;br /&gt;
You can also use one of following named indexes. If the defending target has a matching named palette index defined, it will apply that palette index.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;burn&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;freeze&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;ko&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;shock&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| attack.map.time&lt;br /&gt;
| 0&lt;br /&gt;
| [[Time#Elapsed Time 2|Elapsed time]] to maintain &amp;lt;code&amp;gt;attack.map.index&amp;lt;/code&amp;gt; effect. Target reverts to previous palette afterward.&lt;br /&gt;
|-&lt;br /&gt;
| attack.position.x&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|X position]] of attack box, starting from far left of image. Positive integers move right, negative moves left.&lt;br /&gt;
|-&lt;br /&gt;
| attack.position.y&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|Y position]] of attack box, starting from far top image. Positive integers move down, negative moves up.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.fall.force&lt;br /&gt;
| 0&lt;br /&gt;
| Amount deducted from target’s &#039;&#039;knockdowncount&#039;&#039;. If this value is &amp;lt;code&amp;gt;1+&amp;lt;/code&amp;gt; and target’s &#039;&#039;knockdowncount&#039;&#039; is &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, target is knocked down. Note the knock down check is made immediately after deduction, so if the target has a knockdowncount of &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; and is hit attack with &amp;lt;code&amp;gt;attack.reaction.fall.force 1&amp;lt;/code&amp;gt;, the target falls.&lt;br /&gt;
&amp;lt;br /&amp;gt;&#039;&#039;&#039;Note:&#039;&#039;&#039; Default &#039;&#039;knockdowncount&#039;&#039; is &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, so unless the target is set up with a specific &#039;&#039;knockdowncount&#039;&#039; or some other means to resist falling, setting this value to any causes knockdown.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.fall.velocity.x&lt;br /&gt;
| 1.2&lt;br /&gt;
| [[Geometry Overview|X axis]] toss velocity applied to target if attack knocks it down.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.fall.velocity.y&lt;br /&gt;
| 3.0&lt;br /&gt;
| [[Geometry Overview|Y axis]] toss velocity applied to target if attack knocks it down.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.fall.velocity.z&lt;br /&gt;
| 0.0&lt;br /&gt;
| [[Geometry Overview|Z axis]] toss velocity applied to target if attack knocks it down.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.freeze.mode&lt;br /&gt;
| 0&lt;br /&gt;
| Freeze effect.&amp;lt;br /&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; – No freeze effect.&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; – Target freezes in place.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.freeze.time&lt;br /&gt;
| 0&lt;br /&gt;
| [[Time#Elapsed Time 2|Elapsed time]] to maintain &amp;lt;code&amp;gt;attack.reaction.freeze.mode&amp;lt;/code&amp;gt; effect.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.invincible.time&lt;br /&gt;
| 0&lt;br /&gt;
| [[Time#Elapsed Time 2|Elapsed time]] target is invincible to subsequent hits.&lt;br /&gt;
&amp;lt;br /&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; is allowed and is the default property value. However, the engine will substitute a 0 value with &amp;lt;code&amp;gt;40&amp;lt;/code&amp;gt; when applying the invincibility time. The legacy &#039;&#039;fastattack&#039;&#039; command works by setting this property to &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.pain.disable&lt;br /&gt;
| 0&lt;br /&gt;
| If true, target does not play a reaction animation when hit by attack.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.pause.time&lt;br /&gt;
| 0&lt;br /&gt;
| [[Time#Elapsed Time 2|Elapsed time]] target and attacker pause (freeze) when attack hits.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.reposition.direction&lt;br /&gt;
| None&lt;br /&gt;
| Force target to face specific direction when attack hits.&amp;lt;br /&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;Left&amp;lt;/code&amp;gt; – Target faces left.&lt;br /&gt;
* &amp;lt;code&amp;gt;Opposite&amp;lt;/code&amp;gt; – Target faces opposite direction of attacker.&lt;br /&gt;
* &amp;lt;code&amp;gt;None&amp;lt;/code&amp;gt; – No direction adjustment.&lt;br /&gt;
* &amp;lt;code&amp;gt;Same&amp;lt;/code&amp;gt; – Target faces same direction as attacker.&lt;br /&gt;
* &amp;lt;code&amp;gt;Right&amp;lt;/code&amp;gt; – Target faces right.&lt;br /&gt;
* &amp;lt;code&amp;gt;Toward&amp;lt;/code&amp;gt; – Face toward attacker position.&lt;br /&gt;
* &amp;lt;code&amp;gt;Away&amp;lt;/code&amp;gt; – Face away from attacker position.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.reposition.distance&lt;br /&gt;
| 0&lt;br /&gt;
| Horizontal distance between attacker and target when applying &amp;lt;code&amp;gt;attack.reaction.reposition.mode&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| attack.reaction.reposition.mode&lt;br /&gt;
| 0&lt;br /&gt;
| Horizontal position adjustment when attack hits and does not knock down.&lt;br /&gt;
* &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; – None.&lt;br /&gt;
* &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; - Target moves to attacker.&lt;br /&gt;
* &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt; - Target and attacker move to each other, splitting distance.&lt;br /&gt;
|-&lt;br /&gt;
| attack.seal.cost&lt;br /&gt;
| 0&lt;br /&gt;
| If &amp;lt;code&amp;gt;1+&amp;lt;/code&amp;gt;, this is maximum allowed MP cost target for target specials. For example, if target is hit with seal &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt;, the target may only use specials that cost &amp;lt;code&amp;gt;10&amp;lt;/code&amp;gt; MP or less each.&lt;br /&gt;
|-&lt;br /&gt;
| attack.seal.time&lt;br /&gt;
| 0&lt;br /&gt;
| [[Time#Elapsed Time 2|Elapsed time]] to apply &amp;lt;code&amp;gt;attack.seal.cost&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| attack.size.x&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|X size]] in pixels, moving right from &amp;lt;code&amp;gt;attack.position.x&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| attack.size.y&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|Y size]] in pixels, moving down from &amp;lt;code&amp;gt;attack.position.y&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| attack.size.z.background&lt;br /&gt;
| &#039;&#039;grabdistance&#039;&#039; setting / 3 + 1&lt;br /&gt;
| [[Geometry Overview|Z size]] in pixels, moving further from player’s viewpoint from entity position.&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; For legacy compatibility, it is impossible to set a &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; value using native text. If you do, it is replaced with the default value.&lt;br /&gt;
|-&lt;br /&gt;
| attack.size.z.foreground&lt;br /&gt;
| &#039;&#039;grabdistance&#039;&#039; setting / 3 + 1&lt;br /&gt;
| [[Geometry Overview|Z size]] in pixels, moving toward player’s viewpoint from entity position.&amp;lt;br /&amp;gt;&#039;&#039;&#039;Note:&#039;&#039;&#039; For legacy compatibility, it is impossible to set a &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; value using native text. If you do, it is replaced with the default value.&lt;br /&gt;
|-&lt;br /&gt;
| attack.staydown.rise.time&lt;br /&gt;
| 0&lt;br /&gt;
| [[Time#Elapsed Time 2|Elapsed time]] added to target’s &#039;&#039;risetime&#039;&#039; if knocked down.&lt;br /&gt;
|-&lt;br /&gt;
| attack.staydown.attack.time&lt;br /&gt;
| 0&lt;br /&gt;
| [[Time#Elapsed Time 2|Elapsed time]] before target can use its RISEATTACK animation if knocked down.&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Normally, RISEATTACK is available immediately after a knockdown.&lt;br /&gt;
|-&lt;br /&gt;
| attack.tag&lt;br /&gt;
| 0&lt;br /&gt;
| Creator defined integer ignored by engine logic. Note: Subject to removal on completion of meta_data feature.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
==== Single ====&lt;br /&gt;
&lt;br /&gt;
This example defines a single attack box that knocks down and does 10 points of damage, then closes the box on the subsequent frame. Notice only the necessary properties are defined. Order is irrelevant.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;attack.position.x 58&lt;br /&gt;
attack.position.y 90&lt;br /&gt;
attack.size.x 92&lt;br /&gt;
attack.size.y 25&lt;br /&gt;
attack.reaction.fall.force 1&lt;br /&gt;
attack.reaction.pause.time 10&lt;br /&gt;
attack.damage.force 15&lt;br /&gt;
attack.effect.hit.flash.model flash_fatal_fury&lt;br /&gt;
&lt;br /&gt;
frame data/chars/terry/atk_j_side_kick_c_1.png&lt;br /&gt;
&lt;br /&gt;
attack none&lt;br /&gt;
&lt;br /&gt;
frame data/chars/terry/atk_j_side_kick_c_1.png&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:Fatal fury terry collision atk 2.webp|none|frame|Terry Bogard attacks with a typical hit box.]]&lt;br /&gt;
&lt;br /&gt;
==== Multiple ====&lt;br /&gt;
&lt;br /&gt;
This example takes advantage of multiple boxes to place a more accurate attack area around Ryu’s leg. The second box has an index of 1. Since we didn’t bother to define an index for the first box, it gets a default index of 0. Unless the target is really close, we want the contact area (and thus flash spawns) to be around Ryu’s foot, so we let it have the default index of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to give it higher priority over &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;. The attack definitions are otherwise identical to single boxes. On the following frame, we close both the attack boxes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;attack.damage.force 12&lt;br /&gt;
attack.effect.hit.flash.model flash_fatal_fury&lt;br /&gt;
attack.reaction.fall.force 1&lt;br /&gt;
attack.reaction.pause.time 12&lt;br /&gt;
attack.position.x 138&lt;br /&gt;
attack.position.y 44&lt;br /&gt;
attack.size.x 32&lt;br /&gt;
attack.size.y 26&lt;br /&gt;
&lt;br /&gt;
collision.index 1&lt;br /&gt;
&lt;br /&gt;
attack.damage.force 12&lt;br /&gt;
attack.effect.hit.flash.model flash_fatal_fury&lt;br /&gt;
attack.reaction.fall.force 1&lt;br /&gt;
attack.reaction.pause.time 12&lt;br /&gt;
attack.position.x 112&lt;br /&gt;
attack.position.y 61&lt;br /&gt;
attack.size.x 33&lt;br /&gt;
attack.size.y 25&lt;br /&gt;
&lt;br /&gt;
frame data/chars/ryu/20_3.png&lt;br /&gt;
&lt;br /&gt;
collision.index 0&lt;br /&gt;
attack none&lt;br /&gt;
&lt;br /&gt;
collision.index 1&lt;br /&gt;
attack none&lt;br /&gt;
&lt;br /&gt;
frame data/chars/ryu/20_4.png&amp;lt;/pre&amp;gt;&lt;br /&gt;
[[File:Fatal fury ryu collision atk 0-2.webp|none|frame|Two boxes are better than one?]]&lt;br /&gt;
[[File:Fatal fury ryu collision atk 1-1.webp|none|frame|Take it on the chin!]]&lt;br /&gt;
&#039;&#039;&#039;Tip:&#039;&#039;&#039; As noted above, it is usually an anti-pattern to try and trace collision over the sprite too closely - the multiple box example here is just to demonstrate the principal. In most cases, best practice is to add a body box for head and torso rather than more attack boxes.&lt;br /&gt;
&lt;br /&gt;
== Body ==&lt;br /&gt;
&lt;br /&gt;
Body boxes detect incoming attacks. If an entity doesn’t have a body box on a given frame, it can’t detect attacks and is effectively invincible from conventional hits. Defining a body box is nearly identical to attack boxes, except body boxes are far simpler as they have fewer properties.&lt;br /&gt;
&lt;br /&gt;
To end a body box, add &amp;lt;code&amp;gt;bbox 0&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;bbox none&amp;lt;/code&amp;gt; before a frame.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
|-&lt;br /&gt;
! Command&lt;br /&gt;
! Default&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| bbox.effect.hit.flash.layer.adjust&lt;br /&gt;
| 0&lt;br /&gt;
| Added to global layer adjustment of flash spawn drawing layer. See [https://chronocrash.com/obor/wiki/hit-effects-flash/ Hit Effects].&lt;br /&gt;
|-&lt;br /&gt;
| bbox.effect.hit.flash.layer.source&lt;br /&gt;
| 0&lt;br /&gt;
| Added to global hit flash layer source value for controlling source of flash spawn drawing layer. See [https://chronocrash.com/obor/wiki/hit-effects-flash/ Hit Effects].&lt;br /&gt;
|-&lt;br /&gt;
| bbox.defense&lt;br /&gt;
| None&lt;br /&gt;
| At time of writing, no function. This is a feature in progress that will allow setting up defense properties for individual body boxes.&lt;br /&gt;
|-&lt;br /&gt;
| bbox.meta_data&lt;br /&gt;
| None&lt;br /&gt;
| At time of writing, no function. This is an in progress feature to allow unlimited storage of creator defined data.&lt;br /&gt;
|-&lt;br /&gt;
| bbox.meta_tag&lt;br /&gt;
| 0&lt;br /&gt;
| Creator defined integer ignored by engine logic. Note: Subject to removal on completion of meta_data feature.&lt;br /&gt;
|-&lt;br /&gt;
| bbox.position.x&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|X position]] of body box, starting from far left of image. Positive integers move right, negative moves left.&lt;br /&gt;
|-&lt;br /&gt;
| bbox.position.y&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|Y position]] of body box, starting from top of image. Positive integers move down, negative moves up.&lt;br /&gt;
|-&lt;br /&gt;
| bbox.size.x&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|X size]] in pixels, moving right from &amp;lt;code&amp;gt;body.position.x&amp;lt;/code&amp;gt;.&lt;br /&gt;
|-&lt;br /&gt;
| bbox.size.y&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|Y size]] in pixels, moving down from &amp;lt;code&amp;gt;body.position.y&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| bbox.size.z.background&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|Z size]] in pixels, moving further from player’s viewpoint from entity position.&lt;br /&gt;
|-&lt;br /&gt;
| bbox.size.z.foreground&lt;br /&gt;
| 0&lt;br /&gt;
| [[Geometry Overview|Z size]] in pixels, moving toward player’s viewpoint from entity position.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Example ===&lt;br /&gt;
&lt;br /&gt;
==== Single ====&lt;br /&gt;
&lt;br /&gt;
This example defines a single body box to detect hits.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;bbox.position.x 66&lt;br /&gt;
bbox.position.y 49&lt;br /&gt;
bbox.size.x 38&lt;br /&gt;
bbox.size.y 98&amp;lt;/pre&amp;gt;&lt;br /&gt;
==== Multiple ====&lt;br /&gt;
&lt;br /&gt;
This example takes advantage of multiple boxes to place a more accurate hit area area area around Ryu. The second box has an index of 1, and the third an index of 2. Since we didn’t bother to define an index for the first box, it gets a default index of 0. Unless the target is really close, we want the contact area (and thus flash spawns) to prioritize Ryu’s head, then body, so we place the lower indexed boxes in order from head to feet. The body definitions are otherwise identical to single boxes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;bbox.position.x 78&lt;br /&gt;
bbox.position.y 48&lt;br /&gt;
bbox.size.x 25&lt;br /&gt;
bbox.size.y 21&lt;br /&gt;
&lt;br /&gt;
collision.index 1&lt;br /&gt;
&lt;br /&gt;
bbox.position.x 67&lt;br /&gt;
bbox.position.y 57&lt;br /&gt;
bbox.size.x 35&lt;br /&gt;
bbox.size.y 51&lt;br /&gt;
&lt;br /&gt;
collision.index 2&lt;br /&gt;
&lt;br /&gt;
bbox.position.x 62&lt;br /&gt;
bbox.position.y 103&lt;br /&gt;
bbox.size.x 47&lt;br /&gt;
bbox.size.y 46&lt;br /&gt;
&lt;br /&gt;
frame data/chars/ryu/idle_0.png&lt;br /&gt;
&lt;br /&gt;
collision.index 0&lt;br /&gt;
bbox none&lt;br /&gt;
&lt;br /&gt;
collision.index 1&lt;br /&gt;
bbox none&lt;br /&gt;
&lt;br /&gt;
frame data/chars/ryu/idle_1.png&amp;lt;/pre&amp;gt;&lt;br /&gt;
== Advanced ==&lt;br /&gt;
&lt;br /&gt;
=== Hit Placement ===&lt;br /&gt;
&lt;br /&gt;
Each time a collision occurs, OpenBOR records the location and several other data points into a structure called lasthit. This is then used for items like the native flash effects. Lasthit is available as a system variable and as local vars in didhit and takedamage scripts.&lt;br /&gt;
&lt;br /&gt;
Hit location is the calculated center point where the respective collision boxes overlap on each [[Geometry Overview|Axis]].&lt;br /&gt;
[[File:Collision example 3D 0.webp|none|frame|OpenBOR places a flash effect on the centroid by default, but also exposes the coordinates for creator use.]]&lt;br /&gt;
&lt;br /&gt;
== Legacy Attack ==&lt;br /&gt;
Legacy attack boxes use a single attack command with multiple parameters. Additional parameters are available through separate commands we call “Supplements”. Most legacy attack commands sanitize time inputs into cetiseconds, whereas modern commands accept raw [[Time#Elapsed Time 2|elapsed time]] values.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; Legacy attack commands are depreciated and will not receive further updates.It is highly recommended any new projects utilize the current attack box commands.&lt;br /&gt;
&lt;br /&gt;
=== Base commands ===&lt;br /&gt;
&lt;br /&gt;
==== Attack ====&lt;br /&gt;
Default legacy attack box. Other than &#039;&#039;&#039;{#}&#039;&#039;&#039;, you cannot omit an optional parameter and use any other parameters rightward. For example, if you want to include a &#039;&#039;&#039;{pausetime}&#039;&#039;&#039; value, you must also include &#039;&#039;&#039;{power}&#039;&#039;&#039;, &#039;&#039;&#039;{block}&#039;&#039;&#039;, and &#039;&#039;&#039;{noflash}&#039;&#039;&#039;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;attack{#} {x} {y} {right} {down} {damage} {power} {block} {noflash} {pausetime} {z}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{#}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.type&amp;lt;/code&amp;gt; (numbers only). &#039;&#039;&#039;{#}&#039;&#039;&#039; is part of the command name, no spaces. As an example, if you wanted an attack type 2, you would write &amp;lt;code&amp;gt;attack2 {x} {y} ...&amp;lt;/code&amp;gt;. Optional. If omitted, defaults to 1.&lt;br /&gt;
* &#039;&#039;&#039;{x}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.position.x&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{y}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.position.y&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{right}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.size.x&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{down}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.size.y&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{damage}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.force&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{power}&#039;&#039;&#039; – Optional. See &amp;lt;code&amp;gt;attack.reaction.fall.force&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{block}&#039;&#039;&#039; – Optional. See &amp;lt;code&amp;gt;attack.block.penetrate&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{noflash}&#039;&#039;&#039; – Optional. See &amp;lt;code&amp;gt;attack.effect.hit.flash.disable&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{pausetime}&#039;&#039;&#039; – Optional. See &amp;lt;code&amp;gt;attack.reaction.pause.time&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{z}&#039;&#039;&#039; – Optional. See &amp;lt;code&amp;gt;attack.size.z.1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;attack.size.z.2&amp;lt;/code&amp;gt;. Legacy Z affects both directions.&lt;br /&gt;
&lt;br /&gt;
==== Blast ====&lt;br /&gt;
Same as &#039;&#039;&#039;Attack&#039;&#039;&#039; other than following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;blast {x} {y} {right} {down} {damage} {block} {noflash} {pausetime} {z} &amp;lt;/pre&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.damage.type&amp;lt;/code&amp;gt; = Blast. Note blast type does nothing on its own and defaults to type 1 reactions.&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.reaction.fall.force&amp;lt;/code&amp;gt; = 1.&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.reaction.fall.velocity.x&amp;lt;/code&amp;gt; = is 2.083.&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.damage.mode&amp;lt;/code&amp;gt; = 1.&lt;br /&gt;
&lt;br /&gt;
==== Burn ====&lt;br /&gt;
Same as &#039;&#039;&#039;Attack&#039;&#039;&#039; other than the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;Burn {x} {y} {right} {down} {damage} {knockdown} {block} {noflash} {pausetime} {z}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.damage.type&amp;lt;/code&amp;gt; = Burn.&lt;br /&gt;
&lt;br /&gt;
==== shock ====&lt;br /&gt;
Same as &#039;&#039;&#039;attack#&#039;&#039;&#039; other than the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;shock {x} {y} {right} {down} {damage} {knockdown} {block} {noflash} {pausettime} {z} &amp;lt;/pre&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.damage.type&amp;lt;/code&amp;gt; = Shock.&lt;br /&gt;
&lt;br /&gt;
==== Freeze ====&lt;br /&gt;
Same as &#039;&#039;&#039;Attack&#039;&#039;&#039; other than the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;freeze {x} {y} {right} {down} {damage} {time} {block} {noflash} {pausetime} {z} &amp;lt;/pre&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.damage.type&amp;lt;/code&amp;gt; = Freeze. Note freeze type does nothing on its own and defaults to type 1 reactions.&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.reaction.freeze.mode&amp;lt;/code&amp;gt; = 1.&lt;br /&gt;
* &#039;&#039;&#039;{time}&#039;&#039;&#039; – Time in centiseconds to remain frozen.&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.map.index&amp;lt;/code&amp;gt; = 1.&lt;br /&gt;
&lt;br /&gt;
==== Steal ====&lt;br /&gt;
Same as &#039;&#039;&#039;Attack&#039;&#039;&#039; other than the following:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;steal {x} {y} {right} {down} {damage} {knockdown} {block} {noflash} {pausetime} {z}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.damage.type&amp;lt;/code&amp;gt; = Steal. Note steal type does nothing on its own and defaults to type 1 reactions.&lt;br /&gt;
* &amp;lt;code&amp;gt;attack.damage.steal&amp;lt;/code&amp;gt; = 1.&lt;br /&gt;
&lt;br /&gt;
=== Supplements ===&lt;br /&gt;
&lt;br /&gt;
==== grabin ====&lt;br /&gt;
&amp;lt;pre&amp;gt;grabin {type} {distance}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{type}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reposition.mode&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{distance}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reposition.distance&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Fastattack 1 ====&lt;br /&gt;
When enabled, applies a static value of &amp;lt;code&amp;gt;attack.reaction.invincible.time 10&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Forcedirection ====&lt;br /&gt;
&amp;lt;pre&amp;gt;forcedirection {dir}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{dir}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reposition.direction&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Damageonlanding ====&lt;br /&gt;
&amp;lt;pre&amp;gt;damageonlanding {value} {type} &amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{value}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.land.force&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{type}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.land.mode&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Dropv ====&lt;br /&gt;
&amp;lt;pre&amp;gt;dropv {height} {speedx} {speedz}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{height}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reaction.fall.velocity.y&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{speedx}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reaction.fall.velocity.x&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{speedz}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reaction.fall.velocity.z&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Nokill ====&lt;br /&gt;
&amp;lt;pre&amp;gt;nokill {bool}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{bool}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.lethal.disable&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== stun {int} ====&lt;br /&gt;
* {&#039;&#039;&#039;int}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reaction.freeze.mode&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Seal ====&lt;br /&gt;
&amp;lt;pre&amp;gt;seal {time} {energy} &amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{time}&#039;&#039;&#039; – Time in centiseconds to stay under seal effect.&lt;br /&gt;
* &#039;&#039;&#039;{energy}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.seal.cost&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Forcemap ====&lt;br /&gt;
&amp;lt;pre&amp;gt;forcemap {map} {time}&amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{map}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.map.index&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{time}&#039;&#039;&#039; – Time in centiseconds to keep forcemap.&lt;br /&gt;
&lt;br /&gt;
==== Noreflect ====&lt;br /&gt;
&amp;lt;pre&amp;gt;noreflect {bi} &amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;{bi}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.reaction.pain.disable&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Jugglecost ====&lt;br /&gt;
&amp;lt;pre&amp;gt;jugglecost {int} &amp;lt;/pre&amp;gt;&lt;br /&gt;
* This command limits juggling ability of the attackbox.&lt;br /&gt;
* Juggling means attacking falling opponents (assuming they are vulnerable while falling). It doesn’t matter if the attackbox knocks down or not cause juggling always knock down opponent.&lt;br /&gt;
* This command is used in conjunction with ‘jugglepoints’ (see Header Data above).&lt;br /&gt;
* The command works like this:&lt;br /&gt;
** If attackbox hits opponent whose ‘jugglepoints’ is higher than or equal with ‘jugglecost’, the attack will connect. At this condition, opponent’s ‘jugglepoints’ will be subtracted by that ‘jugglecost’. This drops ‘jugglepoints’ which limits juggling ability. If attackbox hits opponent whose ‘jugglepoints’ is lower than ‘jugglecost’, the attack will not connect. At this condition, opponent’s ‘jugglepoints’ will remain the same.&lt;br /&gt;
&lt;br /&gt;
==== OTG ====&lt;br /&gt;
&amp;lt;pre&amp;gt;OTG {bi}&amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;{bi}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.ground.mode&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Guardcost ====&lt;br /&gt;
&amp;lt;pre&amp;gt;guardcost {int} &amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;{int}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.block.cost&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
* Defines how much ‘guardpoints’ will be subtracted from opponent if they block this attack.&lt;br /&gt;
&lt;br /&gt;
==== DOT ====&lt;br /&gt;
&amp;lt;pre&amp;gt;DOT {Index} {Time} {Mode} {Force} {Rate} &amp;lt;/pre&amp;gt;&lt;br /&gt;
* &#039;&#039;&#039;{index}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.recursive.index&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{time}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.recursive.time&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{mode}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.recursive.mode&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{force}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.recursive.force&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{rate}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.damage.recursive.rate&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
==== Staydown ====&lt;br /&gt;
&amp;lt;pre&amp;gt;staydown {rise} {riseattack} &amp;lt;/pre&amp;gt;&lt;br /&gt;
&#039;&#039;&#039;{rise}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.staydown.rise.time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;{riseattack}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;attack.staydown.attack.time&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
== Legacy Body ==&lt;br /&gt;
&lt;br /&gt;
Legacy body box commands are nearly identical to legacy attack box commands in format.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Note:&#039;&#039;&#039; As with legacy attack commands,legacy body commands are depreciated and will not receive further updates. It is highly recommended any new projects utilize the current body box commands.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;body {x} {y} {width} {height} {depth back} {depth front}&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;{x}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;body.position.x&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{y}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;body.position.y&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{width}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;body.size.x&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{height}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;body.size.y&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{depth back}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;body.size.depth.back&amp;lt;/code&amp;gt;.&lt;br /&gt;
* &#039;&#039;&#039;{depth front}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;body.size.depth.front&amp;lt;/code&amp;gt;. If omitted, &#039;&#039;&#039;{depth back}&#039;&#039;&#039; sets depth in both directions.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;bboxz&#039;&#039;&#039; &#039;&#039;&#039;{depth back} {depth front}&#039;&#039;&#039; – See &amp;lt;code&amp;gt;body.size.depth.back&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;body.size.depth.front&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
[[Category:Game Mechanics]]&lt;br /&gt;
[[Category:Openbor]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1085</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1085"/>
		<updated>2026-08-25T03:40:48Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how far it extends in both directions. Creators do not need to build their own asset handling, controls, movement, artificial intelligence, collision and combat logic, audio playback, or other foundational systems. Everything is integrated and ready to use. Gather the assets and a fully functional game can be running within hours.&lt;br /&gt;
&lt;br /&gt;
At the opposite extreme, OpenBOR allows ground up construction or reconstruction by exposing low-level primitives and engine objects through configuration and script. Creators can inspect runtime state, customize selected behavior, replace complete subsystems, or build new ones without modifying engine source or depending on third-party plugins. Native functionality is therefore a starting point, not a boundary.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1084</id>
		<title>File Operations</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1084"/>
		<updated>2026-08-24T05:44:45Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* String */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;OpenBOR supports file CRUD - create, read, update, and delete - through an API called filestream. Scripts can load external files into memory, read structured text, assemble or revise content, save the result to disk, and remove files that are no longer needed. Common uses include custom configuration, persistent game data, generated model or script files, and data exchanged with external tools.&lt;br /&gt;
&lt;br /&gt;
Despite the name, an OpenBOR filestream is not a continuously open operating-system file. Instead, OpenBOR filestreams use memory-backed buffers. Opening a file loads its contents into memory, after which reading and editing require no additional disk access or filesystem overhead. Scripts may perform any number of operations at memory speed, then explicitly commit the finished buffer to storage with &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Each filestream maintains two independent locations:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Buffer end -&#039;&#039;&#039; &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt; always writes here.&lt;br /&gt;
* &#039;&#039;&#039;Read position -&#039;&#039;&#039; Line, argument, byte, and position functions operate here.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not change where appended data is written.&amp;lt;blockquote&amp;gt;&#039;&#039;&#039;Important:&#039;&#039;&#039; Memory buffers and external files have separate lifecycles. &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; releases a buffer without deleting its saved file. &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; removes an external file without releasing any buffer previously loaded from it.&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lifecycle ==&lt;br /&gt;
Creating, reading, or updating a file normally follows four steps:&lt;br /&gt;
&lt;br /&gt;
# Create an empty stream with &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt;, or load an existing file with &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Read from the current position or append content to the buffer.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; when the buffer should be written to disk.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; when the buffer is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Deletion is independent of this handle lifecycle. Pass a filename directly to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt;; no stream needs to be open.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Operation&lt;br /&gt;
!Native support&lt;br /&gt;
!Method&lt;br /&gt;
|-&lt;br /&gt;
|Create&lt;br /&gt;
|Yes&lt;br /&gt;
|Create an empty buffer, append content, then save it under a filename.&lt;br /&gt;
|-&lt;br /&gt;
|Read&lt;br /&gt;
|Yes&lt;br /&gt;
|Open a file, then read its current line, arguments, or byte.&lt;br /&gt;
|-&lt;br /&gt;
|Update&lt;br /&gt;
|Append or rewrite&lt;br /&gt;
|Open and append to the loaded buffer, or construct a replacement buffer, then save over the previous file.&lt;br /&gt;
|-&lt;br /&gt;
|Delete&lt;br /&gt;
|Yes&lt;br /&gt;
|Pass a filename to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; to remove a writable external file.&lt;br /&gt;
|}&lt;br /&gt;
Saving is explicit. Closing an unsaved stream discards its in-memory changes. Deleting a file does not discard a loaded buffer, so saving that buffer later may recreate the file.&lt;br /&gt;
&lt;br /&gt;
== Handle and Buffer Model ==&lt;br /&gt;
The returned filestream handle is a zero-based index into the engine&#039;s filestream collection. Internally, each entry contains:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Member&lt;br /&gt;
!Purpose&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;buf&amp;lt;/code&amp;gt;&lt;br /&gt;
|Allocated memory containing the loaded or constructed data.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;size&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current buffer size and append location.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;pos&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current byte position used by read functions.&lt;br /&gt;
|}&lt;br /&gt;
Closed entries become available for reuse. Once &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called, the old handle is invalid and may later identify a different filestream.&lt;br /&gt;
&lt;br /&gt;
The engine does not clamp filestream handles or manually assigned positions. Use only handles returned by &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt; or successful calls to &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;, and never use a handle after closing it.&lt;br /&gt;
&lt;br /&gt;
== Walk-through ==&lt;br /&gt;
&lt;br /&gt;
=== Create and Save ===&lt;br /&gt;
The following creates a two-line text file:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = createfilestream();&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;name&amp;quot;, 1);&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;Max&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;score&amp;quot;, 0);&lt;br /&gt;
filestreamappend(filestream_handle, 125000);&lt;br /&gt;
&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default save location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;. Text mode adds a final carriage return and line feed, producing:&amp;lt;pre&amp;gt;&lt;br /&gt;
name Max&lt;br /&gt;
score 125000&lt;br /&gt;
&amp;lt;/pre&amp;gt;In this example, write type &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; adds a trailing space after &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;. Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; begins a new line before &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; and then adds a trailing space.&lt;br /&gt;
&lt;br /&gt;
=== Open and Read ===&lt;br /&gt;
Pass a nonzero location value to load from the current module&#039;s save directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    char player_name = getfilestreamargument(filestream_handle, 1, &amp;quot;string&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
    int player_score = getfilestreamargument(filestream_handle, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The first argument on each line is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. The example reads index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, returning &amp;lt;code&amp;gt;&amp;quot;Max&amp;quot;&amp;lt;/code&amp;gt; from the first line and &amp;lt;code&amp;gt;125000&amp;lt;/code&amp;gt; from the second.&lt;br /&gt;
&lt;br /&gt;
=== Append and Replace ===&lt;br /&gt;
Opening a file loads its existing content into the buffer. Appending new content and saving under the same name rewrites the external file with the expanded buffer:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    /*&lt;br /&gt;
    * Files written by savefilestream() in text mode already&lt;br /&gt;
    * end with a line break, so begin the next record directly.&lt;br /&gt;
    */&lt;br /&gt;
    filestreamappend(filestream_handle, &amp;quot;unlocks&amp;quot;, 1);&lt;br /&gt;
    filestreamappend(filestream_handle, 3);&lt;br /&gt;
&lt;br /&gt;
    savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; opens the destination in write mode and replaces its previous contents. Existing data survives only because &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; first copied that data into the memory buffer.&lt;br /&gt;
&lt;br /&gt;
Filestreams do not provide insertion, replacement, or removal at an arbitrary read position. To modify existing records, read the source and assemble the desired result in another filestream, then save the replacement over the original filename.&lt;br /&gt;
&lt;br /&gt;
=== Delete ===&lt;br /&gt;
Deletion targets the external file by name rather than a filestream handle:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (!deleted) {&lt;br /&gt;
    log(&amp;quot;Could not delete profile.txt.\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;, matching &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. Supply the same optional pathname used to save a file when deleting from a custom location:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&lt;br /&gt;
    &amp;quot;profile.txt&amp;quot;,&lt;br /&gt;
    &amp;quot;data/generated/&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Only the external file is removed. If a filestream already contains data loaded from that file, its memory buffer and handle remain valid until &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
&lt;br /&gt;
=== closefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Releases the filestream&#039;s allocated buffer. The handle becomes invalid and its collection index may be reused by a later create or open operation.&lt;br /&gt;
&lt;br /&gt;
This function does not save the buffer and does not delete an external file.&lt;br /&gt;
&lt;br /&gt;
=== createfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = createfilestream();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Allocates an empty memory buffer and returns its handle. The initial read position and content size are both &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No external file is created until the buffer is passed to &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== deletefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(filename[, pathname]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Deletes a writable external file and returns &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; when the operating system confirms deletion. A return value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; means the file could not be deleted, including when the file does not exist, the path is incorrect, or the operating system denies access.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR targets the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The optional pathname follows the same rules as &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; and is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream( &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The pathname is joined directly to the filename. Include the final slash or backslash.&lt;br /&gt;
&lt;br /&gt;
Deletion operates on external files only and cannot remove content from a packfile. It does not accept a filestream handle, release a memory buffer, or invalidate a handle. Saving a still-open buffer to the same filename after successful deletion creates the file again.&lt;br /&gt;
&lt;br /&gt;
=== filestreamappend() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, value[, write_type[, value_type]]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Converts the supplied value to text and appends it at the end of the buffer. The current read position has no effect on the append location.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;write_type&amp;lt;/code&amp;gt;&lt;br /&gt;
!Behavior&lt;br /&gt;
|-&lt;br /&gt;
|Omitted, &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;, or any value other than &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value without adding a separator.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; before the value, then adds one trailing space after it.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value followed by one trailing space.&lt;br /&gt;
|}&lt;br /&gt;
Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; starts a new line before the supplied value. Using it for the first value in an empty stream therefore creates an initial blank line.&lt;br /&gt;
&lt;br /&gt;
Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;value_type&amp;lt;/code&amp;gt; selects the legacy byte append path:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, byte_value, write_type, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[File Operations#Binary Data|Binary Data]] before using byte mode.&lt;br /&gt;
&lt;br /&gt;
=== filestreamnextline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamnextline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Advances the read position from its current location to the next non-line-break byte. The function scans to the end of the current line, then skips all consecutive &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt; characters.&lt;br /&gt;
&lt;br /&gt;
Multiple empty lines are skipped as one group. At the end of the buffer, the position does not change.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamargument() ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
mixed result = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    result_type&lt;br /&gt;
    [, decode_quotes]&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reads an argument beginning at the current filestream position without advancing the position. Argument indexes are zero-based.&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter controls quote handling. Omit it or pass &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; for legacy whitespace parsing. Pass any nonzero value for modern quote-aware parsing. See [[File Operations#String|String]] for details and examples.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;result_type&amp;lt;/code&amp;gt;&lt;br /&gt;
! Return&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument text.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to an integer.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to a decimal value.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unsigned value of the byte at the current read position. &amp;lt;code&amp;gt;argument_index&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; are ignored.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Result type names are case-insensitive.&lt;br /&gt;
&lt;br /&gt;
==== String ====&lt;br /&gt;
String parsing has two modes selected by the optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter. Legacy behavior remains the default so existing modules continue to work without modification. Quote-aware behavior is available by passing a nonzero fourth parameter.&lt;br /&gt;
&lt;br /&gt;
String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters - about 36 Standard Academic Pages.&lt;br /&gt;
&lt;br /&gt;
===== Legacy Parsing =====&lt;br /&gt;
Omitting &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt;, or passing &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, uses legacy whitespace parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Spaces and tabs always separate arguments. Quotation marks have no special meaning and remain part of the returned text.&lt;br /&gt;
&lt;br /&gt;
Given this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name &amp;quot;Max Thunder&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Legacy parsing returns &amp;lt;code&amp;gt;&amp;quot;Max&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Thunder&amp;quot;&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This behavior supports older scripts that read and reconstruct files one whitespace-delimited token at a time. Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; instead when the complete line and its original formatting must be returned as one string.&lt;br /&gt;
&lt;br /&gt;
===== Quote-Aware Parsing =====&lt;br /&gt;
Pass a nonzero fourth parameter to enable modern quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;,&lt;br /&gt;
    1&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Matching single or double quotes group text into one argument. Opening and closing quote delimiters are discarded from the returned value. Outside quoted spans, spaces and tabs separate arguments, an unquoted line break ends the current record, and &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; begins a comment.&lt;br /&gt;
&lt;br /&gt;
Using the previous example, argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; returns &amp;lt;code&amp;gt;Max Thunder&amp;lt;/code&amp;gt; as one string.&lt;br /&gt;
&lt;br /&gt;
Empty quoted arguments are supported. The maximum string length applies after quote delimiters are removed.&lt;br /&gt;
&lt;br /&gt;
====== Double Quotes ======&lt;br /&gt;
Enclose multiple-word text in double quotes to group it as a single string.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello world&amp;quot;&lt;br /&gt;
Result: Hello world&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
Result: prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Single Quotes ======&lt;br /&gt;
Single quotes may also group text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;Multiple words&#039;&lt;br /&gt;
Result: Multiple words&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: It&#039;s&lt;br /&gt;
Result: It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
====== Mixed Quotes ======&lt;br /&gt;
The opposite quote type remains literal inside a quoted span:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
Result: Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
Result: She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Numeric ====&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
Parsing mode also applies before numeric conversion. Legacy parsing retains quote characters, so a quoted number such as &amp;lt;code&amp;gt;&amp;quot;125&amp;quot;&amp;lt;/code&amp;gt; converts to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Quote-aware parsing removes the delimiters first, allowing the same value to convert to &amp;lt;code&amp;gt;125&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char line = getfilestreamline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns text from the current read position through the next carriage return, line feed, or end of buffer. The line-ending characters are not included.&lt;br /&gt;
&lt;br /&gt;
This function does not advance the read position. Repeated calls return the same line until &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt; changes the position.&lt;br /&gt;
&lt;br /&gt;
An empty line and the end of the buffer both return an empty string.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns the current read position as a byte offset from the beginning of the buffer. The value is not a line number.&lt;br /&gt;
&lt;br /&gt;
Positions may be stored and supplied later to &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== openfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(filename[, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Loads an existing file into memory, sets its read position to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, and returns the filestream handle. The complete file is loaded at once.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;location&amp;lt;/code&amp;gt;&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Module-relative path. OpenBOR checks loose files and the current packfile.&lt;br /&gt;
|-&lt;br /&gt;
|Any nonzero value&lt;br /&gt;
|Current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
For a module named &amp;lt;code&amp;gt;Example.pak&amp;lt;/code&amp;gt;, the following reads &amp;lt;code&amp;gt;Saves/Example/profile.txt&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The function returns &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt; when the requested file does not exist or cannot be opened. Always validate the result before using it:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/config.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;lt; 0) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== savefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, filename [, pathname [, save_type]] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Writes the current buffer to an external file. The destination is opened in write mode, so an existing file at the same path is replaced.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR writes to the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;An optional custom pathname is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot; );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The custom pathname is joined directly to the filename. Include the final slash or backslash in &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Default save paths are recommended for portable persistent data. Custom relative paths may resolve differently between platforms and launch environments.&lt;br /&gt;
&lt;br /&gt;
Text mode writes the buffer and then adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; to the end of the file. Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;save_type&amp;lt;/code&amp;gt; suppresses that final line break:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;output.dat&amp;quot;, &amp;quot;data/generated/&amp;quot;, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Text mode adds this line break unconditionally. If a loaded buffer already ends with &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; and is saved again without new content after it, the file gains another empty line.&lt;br /&gt;
&lt;br /&gt;
Saving does not close the filestream. The same buffer may be appended or saved again before it is released.&lt;br /&gt;
&lt;br /&gt;
=== setfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, position);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Sets the read position to a byte offset. Use &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to return to the beginning:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, 0);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The engine does not validate or clamp this value. Negative positions and positions beyond the allocated buffer are invalid and may cause unstable behavior.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not affect &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt;, which always writes at the buffer end.&lt;br /&gt;
&lt;br /&gt;
== Iterating Through Lines ==&lt;br /&gt;
OpenBOR does not provide a separate end-of-filestream function. Compare the position before and after &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; to detect when no further progress is possible:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/records.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    while (1) {&lt;br /&gt;
        int current_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        char current_line = getfilestreamline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        int next_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        if (current_line != &amp;quot;&amp;quot;) {&lt;br /&gt;
            log(current_line + &amp;quot;\n&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (next_position == current_position) {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This pattern also tolerates empty lines. &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; skips their line-ending characters and continues to the next populated line.&lt;br /&gt;
&lt;br /&gt;
== Binary Data ==&lt;br /&gt;
Byte mode lets scripts work with individual numeric byte values instead of parsed text. This is useful for compact records, custom encodings, byte-level inspection, and data containing non-printing values that are awkward to represent as ordinary strings. Reads return the byte at the current position, while append operations can add byte values directly without converting them to text.&lt;br /&gt;
&lt;br /&gt;
Byte mode is not currently a complete binary-file interface. Saving determines the output length by scanning for a null terminator, so a byte value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; ends the writable content. Values from &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;255&amp;lt;/code&amp;gt; can be manipulated and preserved, but files containing embedded null bytes cannot be saved reliably. Use byte mode for controlled byte-oriented formats, and text mode or another native API when arbitrary binary data must be preserved.&lt;br /&gt;
&lt;br /&gt;
== Practical Guidance ==&lt;br /&gt;
&lt;br /&gt;
* Check every &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; result for &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Keep handles private to the code that owns their lifecycle when practical.&lt;br /&gt;
* Maximum file path length is 511 characters. Be aware that some operating systems impose stricter limits. Windows 11, for example, limits file paths to 260 characters by default. For portability, try to keep file paths reasonable in length.&lt;br /&gt;
* Save before closing when changes must persist.&lt;br /&gt;
* Close every filestream when it is no longer needed.&lt;br /&gt;
* Never use a closed handle or invent a handle index.&lt;br /&gt;
* Check the return value from &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; before assuming a file was removed.&lt;br /&gt;
* Close a buffer separately when deleting the file from which it was loaded.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamposition()&amp;lt;/code&amp;gt; to preserve known-safe positions instead of guessing byte offsets.&lt;br /&gt;
* Prefer one logical record per line and whitespace-separated fields when using &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when field content may contain spaces.&lt;br /&gt;
* Avoid loading unnecessarily large files. Opening a filestream allocates enough memory for the complete file.&lt;br /&gt;
* Remember that packfile content is not modified in place. Save changed data to the module&#039;s save directory or another writable external path.&lt;br /&gt;
&lt;br /&gt;
The engine releases any remaining filestream buffers during shutdown, but does not save them automatically or delete their external files. Explicit cleanup keeps ownership clear and allows closed handle slots to be reused during play.&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
* [[:Category:Script Functions|Script Functions]]&lt;br /&gt;
* [[Arrays]]&lt;br /&gt;
* [[Strings]]&lt;br /&gt;
* [[Variables Overview]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Script]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1083</id>
		<title>File Operations</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1083"/>
		<updated>2026-08-24T05:42:22Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Numeric */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;OpenBOR supports file CRUD - create, read, update, and delete - through an API called filestream. Scripts can load external files into memory, read structured text, assemble or revise content, save the result to disk, and remove files that are no longer needed. Common uses include custom configuration, persistent game data, generated model or script files, and data exchanged with external tools.&lt;br /&gt;
&lt;br /&gt;
Despite the name, an OpenBOR filestream is not a continuously open operating-system file. Instead, OpenBOR filestreams use memory-backed buffers. Opening a file loads its contents into memory, after which reading and editing require no additional disk access or filesystem overhead. Scripts may perform any number of operations at memory speed, then explicitly commit the finished buffer to storage with &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Each filestream maintains two independent locations:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Buffer end -&#039;&#039;&#039; &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt; always writes here.&lt;br /&gt;
* &#039;&#039;&#039;Read position -&#039;&#039;&#039; Line, argument, byte, and position functions operate here.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not change where appended data is written.&amp;lt;blockquote&amp;gt;&#039;&#039;&#039;Important:&#039;&#039;&#039; Memory buffers and external files have separate lifecycles. &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; releases a buffer without deleting its saved file. &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; removes an external file without releasing any buffer previously loaded from it.&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lifecycle ==&lt;br /&gt;
Creating, reading, or updating a file normally follows four steps:&lt;br /&gt;
&lt;br /&gt;
# Create an empty stream with &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt;, or load an existing file with &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Read from the current position or append content to the buffer.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; when the buffer should be written to disk.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; when the buffer is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Deletion is independent of this handle lifecycle. Pass a filename directly to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt;; no stream needs to be open.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Operation&lt;br /&gt;
!Native support&lt;br /&gt;
!Method&lt;br /&gt;
|-&lt;br /&gt;
|Create&lt;br /&gt;
|Yes&lt;br /&gt;
|Create an empty buffer, append content, then save it under a filename.&lt;br /&gt;
|-&lt;br /&gt;
|Read&lt;br /&gt;
|Yes&lt;br /&gt;
|Open a file, then read its current line, arguments, or byte.&lt;br /&gt;
|-&lt;br /&gt;
|Update&lt;br /&gt;
|Append or rewrite&lt;br /&gt;
|Open and append to the loaded buffer, or construct a replacement buffer, then save over the previous file.&lt;br /&gt;
|-&lt;br /&gt;
|Delete&lt;br /&gt;
|Yes&lt;br /&gt;
|Pass a filename to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; to remove a writable external file.&lt;br /&gt;
|}&lt;br /&gt;
Saving is explicit. Closing an unsaved stream discards its in-memory changes. Deleting a file does not discard a loaded buffer, so saving that buffer later may recreate the file.&lt;br /&gt;
&lt;br /&gt;
== Handle and Buffer Model ==&lt;br /&gt;
The returned filestream handle is a zero-based index into the engine&#039;s filestream collection. Internally, each entry contains:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Member&lt;br /&gt;
!Purpose&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;buf&amp;lt;/code&amp;gt;&lt;br /&gt;
|Allocated memory containing the loaded or constructed data.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;size&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current buffer size and append location.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;pos&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current byte position used by read functions.&lt;br /&gt;
|}&lt;br /&gt;
Closed entries become available for reuse. Once &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called, the old handle is invalid and may later identify a different filestream.&lt;br /&gt;
&lt;br /&gt;
The engine does not clamp filestream handles or manually assigned positions. Use only handles returned by &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt; or successful calls to &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;, and never use a handle after closing it.&lt;br /&gt;
&lt;br /&gt;
== Walk-through ==&lt;br /&gt;
&lt;br /&gt;
=== Create and Save ===&lt;br /&gt;
The following creates a two-line text file:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = createfilestream();&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;name&amp;quot;, 1);&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;Max&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;score&amp;quot;, 0);&lt;br /&gt;
filestreamappend(filestream_handle, 125000);&lt;br /&gt;
&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default save location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;. Text mode adds a final carriage return and line feed, producing:&amp;lt;pre&amp;gt;&lt;br /&gt;
name Max&lt;br /&gt;
score 125000&lt;br /&gt;
&amp;lt;/pre&amp;gt;In this example, write type &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; adds a trailing space after &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;. Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; begins a new line before &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; and then adds a trailing space.&lt;br /&gt;
&lt;br /&gt;
=== Open and Read ===&lt;br /&gt;
Pass a nonzero location value to load from the current module&#039;s save directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    char player_name = getfilestreamargument(filestream_handle, 1, &amp;quot;string&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
    int player_score = getfilestreamargument(filestream_handle, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The first argument on each line is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. The example reads index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, returning &amp;lt;code&amp;gt;&amp;quot;Max&amp;quot;&amp;lt;/code&amp;gt; from the first line and &amp;lt;code&amp;gt;125000&amp;lt;/code&amp;gt; from the second.&lt;br /&gt;
&lt;br /&gt;
=== Append and Replace ===&lt;br /&gt;
Opening a file loads its existing content into the buffer. Appending new content and saving under the same name rewrites the external file with the expanded buffer:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    /*&lt;br /&gt;
    * Files written by savefilestream() in text mode already&lt;br /&gt;
    * end with a line break, so begin the next record directly.&lt;br /&gt;
    */&lt;br /&gt;
    filestreamappend(filestream_handle, &amp;quot;unlocks&amp;quot;, 1);&lt;br /&gt;
    filestreamappend(filestream_handle, 3);&lt;br /&gt;
&lt;br /&gt;
    savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; opens the destination in write mode and replaces its previous contents. Existing data survives only because &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; first copied that data into the memory buffer.&lt;br /&gt;
&lt;br /&gt;
Filestreams do not provide insertion, replacement, or removal at an arbitrary read position. To modify existing records, read the source and assemble the desired result in another filestream, then save the replacement over the original filename.&lt;br /&gt;
&lt;br /&gt;
=== Delete ===&lt;br /&gt;
Deletion targets the external file by name rather than a filestream handle:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (!deleted) {&lt;br /&gt;
    log(&amp;quot;Could not delete profile.txt.\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;, matching &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. Supply the same optional pathname used to save a file when deleting from a custom location:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&lt;br /&gt;
    &amp;quot;profile.txt&amp;quot;,&lt;br /&gt;
    &amp;quot;data/generated/&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Only the external file is removed. If a filestream already contains data loaded from that file, its memory buffer and handle remain valid until &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
&lt;br /&gt;
=== closefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Releases the filestream&#039;s allocated buffer. The handle becomes invalid and its collection index may be reused by a later create or open operation.&lt;br /&gt;
&lt;br /&gt;
This function does not save the buffer and does not delete an external file.&lt;br /&gt;
&lt;br /&gt;
=== createfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = createfilestream();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Allocates an empty memory buffer and returns its handle. The initial read position and content size are both &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No external file is created until the buffer is passed to &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== deletefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(filename[, pathname]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Deletes a writable external file and returns &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; when the operating system confirms deletion. A return value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; means the file could not be deleted, including when the file does not exist, the path is incorrect, or the operating system denies access.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR targets the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The optional pathname follows the same rules as &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; and is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream( &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The pathname is joined directly to the filename. Include the final slash or backslash.&lt;br /&gt;
&lt;br /&gt;
Deletion operates on external files only and cannot remove content from a packfile. It does not accept a filestream handle, release a memory buffer, or invalidate a handle. Saving a still-open buffer to the same filename after successful deletion creates the file again.&lt;br /&gt;
&lt;br /&gt;
=== filestreamappend() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, value[, write_type[, value_type]]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Converts the supplied value to text and appends it at the end of the buffer. The current read position has no effect on the append location.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;write_type&amp;lt;/code&amp;gt;&lt;br /&gt;
!Behavior&lt;br /&gt;
|-&lt;br /&gt;
|Omitted, &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;, or any value other than &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value without adding a separator.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; before the value, then adds one trailing space after it.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value followed by one trailing space.&lt;br /&gt;
|}&lt;br /&gt;
Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; starts a new line before the supplied value. Using it for the first value in an empty stream therefore creates an initial blank line.&lt;br /&gt;
&lt;br /&gt;
Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;value_type&amp;lt;/code&amp;gt; selects the legacy byte append path:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, byte_value, write_type, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[File Operations#Binary Data|Binary Data]] before using byte mode.&lt;br /&gt;
&lt;br /&gt;
=== filestreamnextline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamnextline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Advances the read position from its current location to the next non-line-break byte. The function scans to the end of the current line, then skips all consecutive &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt; characters.&lt;br /&gt;
&lt;br /&gt;
Multiple empty lines are skipped as one group. At the end of the buffer, the position does not change.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamargument() ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
mixed result = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    result_type&lt;br /&gt;
    [, decode_quotes]&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reads an argument beginning at the current filestream position without advancing the position. Argument indexes are zero-based.&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter controls quote handling. Omit it or pass &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; for legacy whitespace parsing. Pass any nonzero value for modern quote-aware parsing. See [[File Operations#String|String]] for details and examples.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;result_type&amp;lt;/code&amp;gt;&lt;br /&gt;
! Return&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument text.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to an integer.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to a decimal value.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unsigned value of the byte at the current read position. &amp;lt;code&amp;gt;argument_index&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; are ignored.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Result type names are case-insensitive.&lt;br /&gt;
&lt;br /&gt;
=== String ===&lt;br /&gt;
&lt;br /&gt;
String parsing has two modes selected by the optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter. Legacy behavior remains the default so existing modules continue to work without modification. Quote-aware behavior is available by passing a nonzero fourth parameter.&lt;br /&gt;
&lt;br /&gt;
String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters - about 36 Standard Academic Pages.&lt;br /&gt;
&lt;br /&gt;
==== Legacy Parsing ====&lt;br /&gt;
&lt;br /&gt;
Omitting &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt;, or passing &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, uses legacy whitespace parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Spaces and tabs always separate arguments. Quotation marks have no special meaning and remain part of the returned text.&lt;br /&gt;
&lt;br /&gt;
Given this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name &amp;quot;Max Thunder&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Legacy parsing returns &amp;lt;code&amp;gt;&amp;quot;Max&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Thunder&amp;quot;&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This behavior supports older scripts that read and reconstruct files one whitespace-delimited token at a time. Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; instead when the complete line and its original formatting must be returned as one string.&lt;br /&gt;
&lt;br /&gt;
==== Quote-Aware Parsing ====&lt;br /&gt;
&lt;br /&gt;
Pass a nonzero fourth parameter to enable modern quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;,&lt;br /&gt;
    1&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Matching single or double quotes group text into one argument. Opening and closing quote delimiters are discarded from the returned value. Outside quoted spans, spaces and tabs separate arguments, an unquoted line break ends the current record, and &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; begins a comment.&lt;br /&gt;
&lt;br /&gt;
Using the previous example, argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; returns &amp;lt;code&amp;gt;Max Thunder&amp;lt;/code&amp;gt; as one string.&lt;br /&gt;
&lt;br /&gt;
Empty quoted arguments are supported. The maximum string length applies after quote delimiters are removed.&lt;br /&gt;
&lt;br /&gt;
===== Double Quotes =====&lt;br /&gt;
&lt;br /&gt;
Enclose multiple-word text in double quotes to group it as a single string.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello world&amp;quot;&lt;br /&gt;
Result: Hello world&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
Result: prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Single Quotes =====&lt;br /&gt;
&lt;br /&gt;
Single quotes may also group text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;Multiple words&#039;&lt;br /&gt;
Result: Multiple words&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: It&#039;s&lt;br /&gt;
Result: It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Mixed Quotes =====&lt;br /&gt;
&lt;br /&gt;
The opposite quote type remains literal inside a quoted span:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
Result: Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
Result: She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Numeric ===&lt;br /&gt;
&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
Parsing mode also applies before numeric conversion. Legacy parsing retains quote characters, so a quoted number such as &amp;lt;code&amp;gt;&amp;quot;125&amp;quot;&amp;lt;/code&amp;gt; converts to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Quote-aware parsing removes the delimiters first, allowing the same value to convert to &amp;lt;code&amp;gt;125&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char line = getfilestreamline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns text from the current read position through the next carriage return, line feed, or end of buffer. The line-ending characters are not included.&lt;br /&gt;
&lt;br /&gt;
This function does not advance the read position. Repeated calls return the same line until &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt; changes the position.&lt;br /&gt;
&lt;br /&gt;
An empty line and the end of the buffer both return an empty string.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns the current read position as a byte offset from the beginning of the buffer. The value is not a line number.&lt;br /&gt;
&lt;br /&gt;
Positions may be stored and supplied later to &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== openfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(filename[, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Loads an existing file into memory, sets its read position to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, and returns the filestream handle. The complete file is loaded at once.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;location&amp;lt;/code&amp;gt;&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Module-relative path. OpenBOR checks loose files and the current packfile.&lt;br /&gt;
|-&lt;br /&gt;
|Any nonzero value&lt;br /&gt;
|Current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
For a module named &amp;lt;code&amp;gt;Example.pak&amp;lt;/code&amp;gt;, the following reads &amp;lt;code&amp;gt;Saves/Example/profile.txt&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The function returns &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt; when the requested file does not exist or cannot be opened. Always validate the result before using it:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/config.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;lt; 0) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== savefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, filename [, pathname [, save_type]] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Writes the current buffer to an external file. The destination is opened in write mode, so an existing file at the same path is replaced.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR writes to the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;An optional custom pathname is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot; );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The custom pathname is joined directly to the filename. Include the final slash or backslash in &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Default save paths are recommended for portable persistent data. Custom relative paths may resolve differently between platforms and launch environments.&lt;br /&gt;
&lt;br /&gt;
Text mode writes the buffer and then adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; to the end of the file. Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;save_type&amp;lt;/code&amp;gt; suppresses that final line break:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;output.dat&amp;quot;, &amp;quot;data/generated/&amp;quot;, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Text mode adds this line break unconditionally. If a loaded buffer already ends with &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; and is saved again without new content after it, the file gains another empty line.&lt;br /&gt;
&lt;br /&gt;
Saving does not close the filestream. The same buffer may be appended or saved again before it is released.&lt;br /&gt;
&lt;br /&gt;
=== setfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, position);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Sets the read position to a byte offset. Use &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to return to the beginning:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, 0);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The engine does not validate or clamp this value. Negative positions and positions beyond the allocated buffer are invalid and may cause unstable behavior.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not affect &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt;, which always writes at the buffer end.&lt;br /&gt;
&lt;br /&gt;
== Iterating Through Lines ==&lt;br /&gt;
OpenBOR does not provide a separate end-of-filestream function. Compare the position before and after &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; to detect when no further progress is possible:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/records.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    while (1) {&lt;br /&gt;
        int current_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        char current_line = getfilestreamline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        int next_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        if (current_line != &amp;quot;&amp;quot;) {&lt;br /&gt;
            log(current_line + &amp;quot;\n&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (next_position == current_position) {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This pattern also tolerates empty lines. &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; skips their line-ending characters and continues to the next populated line.&lt;br /&gt;
&lt;br /&gt;
== Binary Data ==&lt;br /&gt;
Byte mode lets scripts work with individual numeric byte values instead of parsed text. This is useful for compact records, custom encodings, byte-level inspection, and data containing non-printing values that are awkward to represent as ordinary strings. Reads return the byte at the current position, while append operations can add byte values directly without converting them to text.&lt;br /&gt;
&lt;br /&gt;
Byte mode is not currently a complete binary-file interface. Saving determines the output length by scanning for a null terminator, so a byte value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; ends the writable content. Values from &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;255&amp;lt;/code&amp;gt; can be manipulated and preserved, but files containing embedded null bytes cannot be saved reliably. Use byte mode for controlled byte-oriented formats, and text mode or another native API when arbitrary binary data must be preserved.&lt;br /&gt;
&lt;br /&gt;
== Practical Guidance ==&lt;br /&gt;
&lt;br /&gt;
* Check every &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; result for &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Keep handles private to the code that owns their lifecycle when practical.&lt;br /&gt;
* Maximum file path length is 511 characters. Be aware that some operating systems impose stricter limits. Windows 11, for example, limits file paths to 260 characters by default. For portability, try to keep file paths reasonable in length.&lt;br /&gt;
* Save before closing when changes must persist.&lt;br /&gt;
* Close every filestream when it is no longer needed.&lt;br /&gt;
* Never use a closed handle or invent a handle index.&lt;br /&gt;
* Check the return value from &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; before assuming a file was removed.&lt;br /&gt;
* Close a buffer separately when deleting the file from which it was loaded.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamposition()&amp;lt;/code&amp;gt; to preserve known-safe positions instead of guessing byte offsets.&lt;br /&gt;
* Prefer one logical record per line and whitespace-separated fields when using &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when field content may contain spaces.&lt;br /&gt;
* Avoid loading unnecessarily large files. Opening a filestream allocates enough memory for the complete file.&lt;br /&gt;
* Remember that packfile content is not modified in place. Save changed data to the module&#039;s save directory or another writable external path.&lt;br /&gt;
&lt;br /&gt;
The engine releases any remaining filestream buffers during shutdown, but does not save them automatically or delete their external files. Explicit cleanup keeps ownership clear and allows closed handle slots to be reused during play.&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
* [[:Category:Script Functions|Script Functions]]&lt;br /&gt;
* [[Arrays]]&lt;br /&gt;
* [[Strings]]&lt;br /&gt;
* [[Variables Overview]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Script]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1082</id>
		<title>File Operations</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1082"/>
		<updated>2026-08-24T05:41:08Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* String */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;OpenBOR supports file CRUD - create, read, update, and delete - through an API called filestream. Scripts can load external files into memory, read structured text, assemble or revise content, save the result to disk, and remove files that are no longer needed. Common uses include custom configuration, persistent game data, generated model or script files, and data exchanged with external tools.&lt;br /&gt;
&lt;br /&gt;
Despite the name, an OpenBOR filestream is not a continuously open operating-system file. Instead, OpenBOR filestreams use memory-backed buffers. Opening a file loads its contents into memory, after which reading and editing require no additional disk access or filesystem overhead. Scripts may perform any number of operations at memory speed, then explicitly commit the finished buffer to storage with &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Each filestream maintains two independent locations:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Buffer end -&#039;&#039;&#039; &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt; always writes here.&lt;br /&gt;
* &#039;&#039;&#039;Read position -&#039;&#039;&#039; Line, argument, byte, and position functions operate here.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not change where appended data is written.&amp;lt;blockquote&amp;gt;&#039;&#039;&#039;Important:&#039;&#039;&#039; Memory buffers and external files have separate lifecycles. &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; releases a buffer without deleting its saved file. &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; removes an external file without releasing any buffer previously loaded from it.&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lifecycle ==&lt;br /&gt;
Creating, reading, or updating a file normally follows four steps:&lt;br /&gt;
&lt;br /&gt;
# Create an empty stream with &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt;, or load an existing file with &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Read from the current position or append content to the buffer.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; when the buffer should be written to disk.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; when the buffer is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Deletion is independent of this handle lifecycle. Pass a filename directly to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt;; no stream needs to be open.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Operation&lt;br /&gt;
!Native support&lt;br /&gt;
!Method&lt;br /&gt;
|-&lt;br /&gt;
|Create&lt;br /&gt;
|Yes&lt;br /&gt;
|Create an empty buffer, append content, then save it under a filename.&lt;br /&gt;
|-&lt;br /&gt;
|Read&lt;br /&gt;
|Yes&lt;br /&gt;
|Open a file, then read its current line, arguments, or byte.&lt;br /&gt;
|-&lt;br /&gt;
|Update&lt;br /&gt;
|Append or rewrite&lt;br /&gt;
|Open and append to the loaded buffer, or construct a replacement buffer, then save over the previous file.&lt;br /&gt;
|-&lt;br /&gt;
|Delete&lt;br /&gt;
|Yes&lt;br /&gt;
|Pass a filename to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; to remove a writable external file.&lt;br /&gt;
|}&lt;br /&gt;
Saving is explicit. Closing an unsaved stream discards its in-memory changes. Deleting a file does not discard a loaded buffer, so saving that buffer later may recreate the file.&lt;br /&gt;
&lt;br /&gt;
== Handle and Buffer Model ==&lt;br /&gt;
The returned filestream handle is a zero-based index into the engine&#039;s filestream collection. Internally, each entry contains:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Member&lt;br /&gt;
!Purpose&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;buf&amp;lt;/code&amp;gt;&lt;br /&gt;
|Allocated memory containing the loaded or constructed data.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;size&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current buffer size and append location.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;pos&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current byte position used by read functions.&lt;br /&gt;
|}&lt;br /&gt;
Closed entries become available for reuse. Once &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called, the old handle is invalid and may later identify a different filestream.&lt;br /&gt;
&lt;br /&gt;
The engine does not clamp filestream handles or manually assigned positions. Use only handles returned by &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt; or successful calls to &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;, and never use a handle after closing it.&lt;br /&gt;
&lt;br /&gt;
== Walk-through ==&lt;br /&gt;
&lt;br /&gt;
=== Create and Save ===&lt;br /&gt;
The following creates a two-line text file:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = createfilestream();&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;name&amp;quot;, 1);&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;Max&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;score&amp;quot;, 0);&lt;br /&gt;
filestreamappend(filestream_handle, 125000);&lt;br /&gt;
&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default save location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;. Text mode adds a final carriage return and line feed, producing:&amp;lt;pre&amp;gt;&lt;br /&gt;
name Max&lt;br /&gt;
score 125000&lt;br /&gt;
&amp;lt;/pre&amp;gt;In this example, write type &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; adds a trailing space after &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;. Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; begins a new line before &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; and then adds a trailing space.&lt;br /&gt;
&lt;br /&gt;
=== Open and Read ===&lt;br /&gt;
Pass a nonzero location value to load from the current module&#039;s save directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    char player_name = getfilestreamargument(filestream_handle, 1, &amp;quot;string&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
    int player_score = getfilestreamargument(filestream_handle, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The first argument on each line is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. The example reads index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, returning &amp;lt;code&amp;gt;&amp;quot;Max&amp;quot;&amp;lt;/code&amp;gt; from the first line and &amp;lt;code&amp;gt;125000&amp;lt;/code&amp;gt; from the second.&lt;br /&gt;
&lt;br /&gt;
=== Append and Replace ===&lt;br /&gt;
Opening a file loads its existing content into the buffer. Appending new content and saving under the same name rewrites the external file with the expanded buffer:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    /*&lt;br /&gt;
    * Files written by savefilestream() in text mode already&lt;br /&gt;
    * end with a line break, so begin the next record directly.&lt;br /&gt;
    */&lt;br /&gt;
    filestreamappend(filestream_handle, &amp;quot;unlocks&amp;quot;, 1);&lt;br /&gt;
    filestreamappend(filestream_handle, 3);&lt;br /&gt;
&lt;br /&gt;
    savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; opens the destination in write mode and replaces its previous contents. Existing data survives only because &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; first copied that data into the memory buffer.&lt;br /&gt;
&lt;br /&gt;
Filestreams do not provide insertion, replacement, or removal at an arbitrary read position. To modify existing records, read the source and assemble the desired result in another filestream, then save the replacement over the original filename.&lt;br /&gt;
&lt;br /&gt;
=== Delete ===&lt;br /&gt;
Deletion targets the external file by name rather than a filestream handle:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (!deleted) {&lt;br /&gt;
    log(&amp;quot;Could not delete profile.txt.\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;, matching &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. Supply the same optional pathname used to save a file when deleting from a custom location:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&lt;br /&gt;
    &amp;quot;profile.txt&amp;quot;,&lt;br /&gt;
    &amp;quot;data/generated/&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Only the external file is removed. If a filestream already contains data loaded from that file, its memory buffer and handle remain valid until &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
&lt;br /&gt;
=== closefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Releases the filestream&#039;s allocated buffer. The handle becomes invalid and its collection index may be reused by a later create or open operation.&lt;br /&gt;
&lt;br /&gt;
This function does not save the buffer and does not delete an external file.&lt;br /&gt;
&lt;br /&gt;
=== createfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = createfilestream();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Allocates an empty memory buffer and returns its handle. The initial read position and content size are both &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No external file is created until the buffer is passed to &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== deletefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(filename[, pathname]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Deletes a writable external file and returns &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; when the operating system confirms deletion. A return value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; means the file could not be deleted, including when the file does not exist, the path is incorrect, or the operating system denies access.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR targets the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The optional pathname follows the same rules as &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; and is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream( &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The pathname is joined directly to the filename. Include the final slash or backslash.&lt;br /&gt;
&lt;br /&gt;
Deletion operates on external files only and cannot remove content from a packfile. It does not accept a filestream handle, release a memory buffer, or invalidate a handle. Saving a still-open buffer to the same filename after successful deletion creates the file again.&lt;br /&gt;
&lt;br /&gt;
=== filestreamappend() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, value[, write_type[, value_type]]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Converts the supplied value to text and appends it at the end of the buffer. The current read position has no effect on the append location.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;write_type&amp;lt;/code&amp;gt;&lt;br /&gt;
!Behavior&lt;br /&gt;
|-&lt;br /&gt;
|Omitted, &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;, or any value other than &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value without adding a separator.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; before the value, then adds one trailing space after it.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value followed by one trailing space.&lt;br /&gt;
|}&lt;br /&gt;
Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; starts a new line before the supplied value. Using it for the first value in an empty stream therefore creates an initial blank line.&lt;br /&gt;
&lt;br /&gt;
Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;value_type&amp;lt;/code&amp;gt; selects the legacy byte append path:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, byte_value, write_type, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[File Operations#Binary Data|Binary Data]] before using byte mode.&lt;br /&gt;
&lt;br /&gt;
=== filestreamnextline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamnextline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Advances the read position from its current location to the next non-line-break byte. The function scans to the end of the current line, then skips all consecutive &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt; characters.&lt;br /&gt;
&lt;br /&gt;
Multiple empty lines are skipped as one group. At the end of the buffer, the position does not change.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamargument() ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
mixed result = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    result_type&lt;br /&gt;
    [, decode_quotes]&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reads an argument beginning at the current filestream position without advancing the position. Argument indexes are zero-based.&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter controls quote handling. Omit it or pass &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; for legacy whitespace parsing. Pass any nonzero value for modern quote-aware parsing. See [[File Operations#String|String]] for details and examples.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;result_type&amp;lt;/code&amp;gt;&lt;br /&gt;
! Return&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument text.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to an integer.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to a decimal value.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unsigned value of the byte at the current read position. &amp;lt;code&amp;gt;argument_index&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; are ignored.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Result type names are case-insensitive.&lt;br /&gt;
&lt;br /&gt;
=== String ===&lt;br /&gt;
&lt;br /&gt;
String parsing has two modes selected by the optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter. Legacy behavior remains the default so existing modules continue to work without modification. Quote-aware behavior is available by passing a nonzero fourth parameter.&lt;br /&gt;
&lt;br /&gt;
String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters - about 36 Standard Academic Pages.&lt;br /&gt;
&lt;br /&gt;
==== Legacy Parsing ====&lt;br /&gt;
&lt;br /&gt;
Omitting &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt;, or passing &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, uses legacy whitespace parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Spaces and tabs always separate arguments. Quotation marks have no special meaning and remain part of the returned text.&lt;br /&gt;
&lt;br /&gt;
Given this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name &amp;quot;Max Thunder&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Legacy parsing returns &amp;lt;code&amp;gt;&amp;quot;Max&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Thunder&amp;quot;&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This behavior supports older scripts that read and reconstruct files one whitespace-delimited token at a time. Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; instead when the complete line and its original formatting must be returned as one string.&lt;br /&gt;
&lt;br /&gt;
==== Quote-Aware Parsing ====&lt;br /&gt;
&lt;br /&gt;
Pass a nonzero fourth parameter to enable modern quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;,&lt;br /&gt;
    1&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Matching single or double quotes group text into one argument. Opening and closing quote delimiters are discarded from the returned value. Outside quoted spans, spaces and tabs separate arguments, an unquoted line break ends the current record, and &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; begins a comment.&lt;br /&gt;
&lt;br /&gt;
Using the previous example, argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; returns &amp;lt;code&amp;gt;Max Thunder&amp;lt;/code&amp;gt; as one string.&lt;br /&gt;
&lt;br /&gt;
Empty quoted arguments are supported. The maximum string length applies after quote delimiters are removed.&lt;br /&gt;
&lt;br /&gt;
===== Double Quotes =====&lt;br /&gt;
&lt;br /&gt;
Enclose multiple-word text in double quotes to group it as a single string.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello world&amp;quot;&lt;br /&gt;
Result: Hello world&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
Result: prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Single Quotes =====&lt;br /&gt;
&lt;br /&gt;
Single quotes may also group text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;Multiple words&#039;&lt;br /&gt;
Result: Multiple words&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: It&#039;s&lt;br /&gt;
Result: It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Mixed Quotes =====&lt;br /&gt;
&lt;br /&gt;
The opposite quote type remains literal inside a quoted span:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
Result: Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
Result: She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Numeric ===&lt;br /&gt;
&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
Parsing mode also applies before numeric conversion. Legacy parsing retains quote characters, so a quoted number such as &amp;lt;code&amp;gt;&amp;quot;125&amp;quot;&amp;lt;/code&amp;gt; converts to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Quote-aware parsing removes the delimiters first, allowing the same value to convert to &amp;lt;code&amp;gt;125&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== Numeric ===&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char line = getfilestreamline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns text from the current read position through the next carriage return, line feed, or end of buffer. The line-ending characters are not included.&lt;br /&gt;
&lt;br /&gt;
This function does not advance the read position. Repeated calls return the same line until &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt; changes the position.&lt;br /&gt;
&lt;br /&gt;
An empty line and the end of the buffer both return an empty string.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns the current read position as a byte offset from the beginning of the buffer. The value is not a line number.&lt;br /&gt;
&lt;br /&gt;
Positions may be stored and supplied later to &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== openfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(filename[, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Loads an existing file into memory, sets its read position to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, and returns the filestream handle. The complete file is loaded at once.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;location&amp;lt;/code&amp;gt;&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Module-relative path. OpenBOR checks loose files and the current packfile.&lt;br /&gt;
|-&lt;br /&gt;
|Any nonzero value&lt;br /&gt;
|Current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
For a module named &amp;lt;code&amp;gt;Example.pak&amp;lt;/code&amp;gt;, the following reads &amp;lt;code&amp;gt;Saves/Example/profile.txt&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The function returns &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt; when the requested file does not exist or cannot be opened. Always validate the result before using it:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/config.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;lt; 0) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== savefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, filename [, pathname [, save_type]] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Writes the current buffer to an external file. The destination is opened in write mode, so an existing file at the same path is replaced.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR writes to the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;An optional custom pathname is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot; );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The custom pathname is joined directly to the filename. Include the final slash or backslash in &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Default save paths are recommended for portable persistent data. Custom relative paths may resolve differently between platforms and launch environments.&lt;br /&gt;
&lt;br /&gt;
Text mode writes the buffer and then adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; to the end of the file. Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;save_type&amp;lt;/code&amp;gt; suppresses that final line break:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;output.dat&amp;quot;, &amp;quot;data/generated/&amp;quot;, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Text mode adds this line break unconditionally. If a loaded buffer already ends with &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; and is saved again without new content after it, the file gains another empty line.&lt;br /&gt;
&lt;br /&gt;
Saving does not close the filestream. The same buffer may be appended or saved again before it is released.&lt;br /&gt;
&lt;br /&gt;
=== setfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, position);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Sets the read position to a byte offset. Use &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to return to the beginning:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, 0);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The engine does not validate or clamp this value. Negative positions and positions beyond the allocated buffer are invalid and may cause unstable behavior.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not affect &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt;, which always writes at the buffer end.&lt;br /&gt;
&lt;br /&gt;
== Iterating Through Lines ==&lt;br /&gt;
OpenBOR does not provide a separate end-of-filestream function. Compare the position before and after &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; to detect when no further progress is possible:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/records.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    while (1) {&lt;br /&gt;
        int current_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        char current_line = getfilestreamline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        int next_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        if (current_line != &amp;quot;&amp;quot;) {&lt;br /&gt;
            log(current_line + &amp;quot;\n&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (next_position == current_position) {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This pattern also tolerates empty lines. &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; skips their line-ending characters and continues to the next populated line.&lt;br /&gt;
&lt;br /&gt;
== Binary Data ==&lt;br /&gt;
Byte mode lets scripts work with individual numeric byte values instead of parsed text. This is useful for compact records, custom encodings, byte-level inspection, and data containing non-printing values that are awkward to represent as ordinary strings. Reads return the byte at the current position, while append operations can add byte values directly without converting them to text.&lt;br /&gt;
&lt;br /&gt;
Byte mode is not currently a complete binary-file interface. Saving determines the output length by scanning for a null terminator, so a byte value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; ends the writable content. Values from &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;255&amp;lt;/code&amp;gt; can be manipulated and preserved, but files containing embedded null bytes cannot be saved reliably. Use byte mode for controlled byte-oriented formats, and text mode or another native API when arbitrary binary data must be preserved.&lt;br /&gt;
&lt;br /&gt;
== Practical Guidance ==&lt;br /&gt;
&lt;br /&gt;
* Check every &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; result for &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Keep handles private to the code that owns their lifecycle when practical.&lt;br /&gt;
* Maximum file path length is 511 characters. Be aware that some operating systems impose stricter limits. Windows 11, for example, limits file paths to 260 characters by default. For portability, try to keep file paths reasonable in length.&lt;br /&gt;
* Save before closing when changes must persist.&lt;br /&gt;
* Close every filestream when it is no longer needed.&lt;br /&gt;
* Never use a closed handle or invent a handle index.&lt;br /&gt;
* Check the return value from &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; before assuming a file was removed.&lt;br /&gt;
* Close a buffer separately when deleting the file from which it was loaded.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamposition()&amp;lt;/code&amp;gt; to preserve known-safe positions instead of guessing byte offsets.&lt;br /&gt;
* Prefer one logical record per line and whitespace-separated fields when using &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when field content may contain spaces.&lt;br /&gt;
* Avoid loading unnecessarily large files. Opening a filestream allocates enough memory for the complete file.&lt;br /&gt;
* Remember that packfile content is not modified in place. Save changed data to the module&#039;s save directory or another writable external path.&lt;br /&gt;
&lt;br /&gt;
The engine releases any remaining filestream buffers during shutdown, but does not save them automatically or delete their external files. Explicit cleanup keeps ownership clear and allows closed handle slots to be reused during play.&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
* [[:Category:Script Functions|Script Functions]]&lt;br /&gt;
* [[Arrays]]&lt;br /&gt;
* [[Strings]]&lt;br /&gt;
* [[Variables Overview]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Script]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1081</id>
		<title>File Operations</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1081"/>
		<updated>2026-08-24T05:40:17Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* getfilestreamargument() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;OpenBOR supports file CRUD - create, read, update, and delete - through an API called filestream. Scripts can load external files into memory, read structured text, assemble or revise content, save the result to disk, and remove files that are no longer needed. Common uses include custom configuration, persistent game data, generated model or script files, and data exchanged with external tools.&lt;br /&gt;
&lt;br /&gt;
Despite the name, an OpenBOR filestream is not a continuously open operating-system file. Instead, OpenBOR filestreams use memory-backed buffers. Opening a file loads its contents into memory, after which reading and editing require no additional disk access or filesystem overhead. Scripts may perform any number of operations at memory speed, then explicitly commit the finished buffer to storage with &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Each filestream maintains two independent locations:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Buffer end -&#039;&#039;&#039; &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt; always writes here.&lt;br /&gt;
* &#039;&#039;&#039;Read position -&#039;&#039;&#039; Line, argument, byte, and position functions operate here.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not change where appended data is written.&amp;lt;blockquote&amp;gt;&#039;&#039;&#039;Important:&#039;&#039;&#039; Memory buffers and external files have separate lifecycles. &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; releases a buffer without deleting its saved file. &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; removes an external file without releasing any buffer previously loaded from it.&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lifecycle ==&lt;br /&gt;
Creating, reading, or updating a file normally follows four steps:&lt;br /&gt;
&lt;br /&gt;
# Create an empty stream with &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt;, or load an existing file with &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Read from the current position or append content to the buffer.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; when the buffer should be written to disk.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; when the buffer is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Deletion is independent of this handle lifecycle. Pass a filename directly to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt;; no stream needs to be open.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Operation&lt;br /&gt;
!Native support&lt;br /&gt;
!Method&lt;br /&gt;
|-&lt;br /&gt;
|Create&lt;br /&gt;
|Yes&lt;br /&gt;
|Create an empty buffer, append content, then save it under a filename.&lt;br /&gt;
|-&lt;br /&gt;
|Read&lt;br /&gt;
|Yes&lt;br /&gt;
|Open a file, then read its current line, arguments, or byte.&lt;br /&gt;
|-&lt;br /&gt;
|Update&lt;br /&gt;
|Append or rewrite&lt;br /&gt;
|Open and append to the loaded buffer, or construct a replacement buffer, then save over the previous file.&lt;br /&gt;
|-&lt;br /&gt;
|Delete&lt;br /&gt;
|Yes&lt;br /&gt;
|Pass a filename to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; to remove a writable external file.&lt;br /&gt;
|}&lt;br /&gt;
Saving is explicit. Closing an unsaved stream discards its in-memory changes. Deleting a file does not discard a loaded buffer, so saving that buffer later may recreate the file.&lt;br /&gt;
&lt;br /&gt;
== Handle and Buffer Model ==&lt;br /&gt;
The returned filestream handle is a zero-based index into the engine&#039;s filestream collection. Internally, each entry contains:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Member&lt;br /&gt;
!Purpose&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;buf&amp;lt;/code&amp;gt;&lt;br /&gt;
|Allocated memory containing the loaded or constructed data.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;size&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current buffer size and append location.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;pos&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current byte position used by read functions.&lt;br /&gt;
|}&lt;br /&gt;
Closed entries become available for reuse. Once &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called, the old handle is invalid and may later identify a different filestream.&lt;br /&gt;
&lt;br /&gt;
The engine does not clamp filestream handles or manually assigned positions. Use only handles returned by &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt; or successful calls to &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;, and never use a handle after closing it.&lt;br /&gt;
&lt;br /&gt;
== Walk-through ==&lt;br /&gt;
&lt;br /&gt;
=== Create and Save ===&lt;br /&gt;
The following creates a two-line text file:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = createfilestream();&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;name&amp;quot;, 1);&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;Max&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;score&amp;quot;, 0);&lt;br /&gt;
filestreamappend(filestream_handle, 125000);&lt;br /&gt;
&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default save location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;. Text mode adds a final carriage return and line feed, producing:&amp;lt;pre&amp;gt;&lt;br /&gt;
name Max&lt;br /&gt;
score 125000&lt;br /&gt;
&amp;lt;/pre&amp;gt;In this example, write type &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; adds a trailing space after &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;. Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; begins a new line before &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; and then adds a trailing space.&lt;br /&gt;
&lt;br /&gt;
=== Open and Read ===&lt;br /&gt;
Pass a nonzero location value to load from the current module&#039;s save directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    char player_name = getfilestreamargument(filestream_handle, 1, &amp;quot;string&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
    int player_score = getfilestreamargument(filestream_handle, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The first argument on each line is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. The example reads index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, returning &amp;lt;code&amp;gt;&amp;quot;Max&amp;quot;&amp;lt;/code&amp;gt; from the first line and &amp;lt;code&amp;gt;125000&amp;lt;/code&amp;gt; from the second.&lt;br /&gt;
&lt;br /&gt;
=== Append and Replace ===&lt;br /&gt;
Opening a file loads its existing content into the buffer. Appending new content and saving under the same name rewrites the external file with the expanded buffer:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    /*&lt;br /&gt;
    * Files written by savefilestream() in text mode already&lt;br /&gt;
    * end with a line break, so begin the next record directly.&lt;br /&gt;
    */&lt;br /&gt;
    filestreamappend(filestream_handle, &amp;quot;unlocks&amp;quot;, 1);&lt;br /&gt;
    filestreamappend(filestream_handle, 3);&lt;br /&gt;
&lt;br /&gt;
    savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; opens the destination in write mode and replaces its previous contents. Existing data survives only because &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; first copied that data into the memory buffer.&lt;br /&gt;
&lt;br /&gt;
Filestreams do not provide insertion, replacement, or removal at an arbitrary read position. To modify existing records, read the source and assemble the desired result in another filestream, then save the replacement over the original filename.&lt;br /&gt;
&lt;br /&gt;
=== Delete ===&lt;br /&gt;
Deletion targets the external file by name rather than a filestream handle:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (!deleted) {&lt;br /&gt;
    log(&amp;quot;Could not delete profile.txt.\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;, matching &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. Supply the same optional pathname used to save a file when deleting from a custom location:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&lt;br /&gt;
    &amp;quot;profile.txt&amp;quot;,&lt;br /&gt;
    &amp;quot;data/generated/&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Only the external file is removed. If a filestream already contains data loaded from that file, its memory buffer and handle remain valid until &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
&lt;br /&gt;
=== closefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Releases the filestream&#039;s allocated buffer. The handle becomes invalid and its collection index may be reused by a later create or open operation.&lt;br /&gt;
&lt;br /&gt;
This function does not save the buffer and does not delete an external file.&lt;br /&gt;
&lt;br /&gt;
=== createfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = createfilestream();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Allocates an empty memory buffer and returns its handle. The initial read position and content size are both &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No external file is created until the buffer is passed to &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== deletefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(filename[, pathname]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Deletes a writable external file and returns &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; when the operating system confirms deletion. A return value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; means the file could not be deleted, including when the file does not exist, the path is incorrect, or the operating system denies access.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR targets the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The optional pathname follows the same rules as &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; and is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream( &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The pathname is joined directly to the filename. Include the final slash or backslash.&lt;br /&gt;
&lt;br /&gt;
Deletion operates on external files only and cannot remove content from a packfile. It does not accept a filestream handle, release a memory buffer, or invalidate a handle. Saving a still-open buffer to the same filename after successful deletion creates the file again.&lt;br /&gt;
&lt;br /&gt;
=== filestreamappend() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, value[, write_type[, value_type]]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Converts the supplied value to text and appends it at the end of the buffer. The current read position has no effect on the append location.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;write_type&amp;lt;/code&amp;gt;&lt;br /&gt;
!Behavior&lt;br /&gt;
|-&lt;br /&gt;
|Omitted, &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;, or any value other than &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value without adding a separator.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; before the value, then adds one trailing space after it.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value followed by one trailing space.&lt;br /&gt;
|}&lt;br /&gt;
Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; starts a new line before the supplied value. Using it for the first value in an empty stream therefore creates an initial blank line.&lt;br /&gt;
&lt;br /&gt;
Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;value_type&amp;lt;/code&amp;gt; selects the legacy byte append path:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, byte_value, write_type, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[File Operations#Binary Data|Binary Data]] before using byte mode.&lt;br /&gt;
&lt;br /&gt;
=== filestreamnextline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamnextline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Advances the read position from its current location to the next non-line-break byte. The function scans to the end of the current line, then skips all consecutive &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt; characters.&lt;br /&gt;
&lt;br /&gt;
Multiple empty lines are skipped as one group. At the end of the buffer, the position does not change.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamargument() ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
mixed result = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    result_type&lt;br /&gt;
    [, decode_quotes]&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reads an argument beginning at the current filestream position without advancing the position. Argument indexes are zero-based.&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter controls quote handling. Omit it or pass &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; for legacy whitespace parsing. Pass any nonzero value for modern quote-aware parsing. See [[File Operations#String|String]] for details and examples.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;result_type&amp;lt;/code&amp;gt;&lt;br /&gt;
! Return&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument text.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to an integer.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to a decimal value.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unsigned value of the byte at the current read position. &amp;lt;code&amp;gt;argument_index&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; are ignored.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Result type names are case-insensitive.&lt;br /&gt;
&lt;br /&gt;
=== String ===&lt;br /&gt;
&lt;br /&gt;
String parsing has two modes selected by the optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter. Legacy behavior remains the default so existing modules continue to work without modification. Quote-aware behavior is available by passing a nonzero fourth parameter.&lt;br /&gt;
&lt;br /&gt;
String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters - about 36 Standard Academic Pages.&lt;br /&gt;
&lt;br /&gt;
==== Legacy Parsing ====&lt;br /&gt;
&lt;br /&gt;
Omitting &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt;, or passing &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, uses legacy whitespace parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Spaces and tabs always separate arguments. Quotation marks have no special meaning and remain part of the returned text.&lt;br /&gt;
&lt;br /&gt;
Given this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name &amp;quot;Max Thunder&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Legacy parsing returns &amp;lt;code&amp;gt;&amp;quot;Max&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;Thunder&amp;quot;&amp;lt;/code&amp;gt; at argument index &amp;lt;code&amp;gt;2&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
This behavior supports older scripts that read and reconstruct files one whitespace-delimited token at a time. Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; instead when the complete line and its original formatting must be returned as one string.&lt;br /&gt;
&lt;br /&gt;
==== Quote-Aware Parsing ====&lt;br /&gt;
&lt;br /&gt;
Pass a nonzero fourth parameter to enable modern quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;,&lt;br /&gt;
    1&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Matching single or double quotes group text into one argument. Opening and closing quote delimiters are discarded from the returned value. Outside quoted spans, spaces and tabs separate arguments, an unquoted line break ends the current record, and &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; begins a comment.&lt;br /&gt;
&lt;br /&gt;
Using the previous example, argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; returns &amp;lt;code&amp;gt;Max Thunder&amp;lt;/code&amp;gt; as one string.&lt;br /&gt;
&lt;br /&gt;
Empty quoted arguments are supported. The maximum string length applies after quote delimiters are removed.&lt;br /&gt;
&lt;br /&gt;
===== Double Quotes =====&lt;br /&gt;
&lt;br /&gt;
Enclose multiple-word text in double quotes to group it as a single string.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello world&amp;quot;&lt;br /&gt;
Result: Hello world&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
Result: prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Single Quotes =====&lt;br /&gt;
&lt;br /&gt;
Single quotes may also group text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;Multiple words&#039;&lt;br /&gt;
Result: Multiple words&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: It&#039;s&lt;br /&gt;
Result: It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Mixed Quotes =====&lt;br /&gt;
&lt;br /&gt;
The opposite quote type remains literal inside a quoted span:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
Result: Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Value: &#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
Result: She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Numeric ===&lt;br /&gt;
&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
Parsing mode also applies before numeric conversion. Legacy parsing retains quote characters, so a quoted number such as &amp;lt;code&amp;gt;&amp;quot;125&amp;quot;&amp;lt;/code&amp;gt; converts to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Quote-aware parsing removes the delimiters first, allowing the same value to convert to &amp;lt;code&amp;gt;125&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== String ===&lt;br /&gt;
Matching single or double quotes group text into one argument. Opening and closing quote delimiters are discarded from the returned value. String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters - about 36 Standard Academic Pages. The limit applies after quote delimiters are removed.&lt;br /&gt;
&lt;br /&gt;
==== Double Quotes ====&lt;br /&gt;
Enclose multiple word tokens in quotes to group as a single string.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello world&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Result: Hello world&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
&lt;br /&gt;
Result: prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Single Quotes ====&lt;br /&gt;
You may also enclose tokens with single quotes.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &#039;Its&#039;&lt;br /&gt;
&lt;br /&gt;
Result: Its&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: It&#039;s&lt;br /&gt;
&lt;br /&gt;
Result: It&#039;s&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Mixed Quotes ====&lt;br /&gt;
You may mix quote types to preserve quotations within the string. The opposite quote type remains literal inside a quoted span:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Result: Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Value: &#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
&lt;br /&gt;
Result: She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Numeric ===&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char line = getfilestreamline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns text from the current read position through the next carriage return, line feed, or end of buffer. The line-ending characters are not included.&lt;br /&gt;
&lt;br /&gt;
This function does not advance the read position. Repeated calls return the same line until &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt; changes the position.&lt;br /&gt;
&lt;br /&gt;
An empty line and the end of the buffer both return an empty string.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns the current read position as a byte offset from the beginning of the buffer. The value is not a line number.&lt;br /&gt;
&lt;br /&gt;
Positions may be stored and supplied later to &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== openfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(filename[, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Loads an existing file into memory, sets its read position to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, and returns the filestream handle. The complete file is loaded at once.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;location&amp;lt;/code&amp;gt;&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Module-relative path. OpenBOR checks loose files and the current packfile.&lt;br /&gt;
|-&lt;br /&gt;
|Any nonzero value&lt;br /&gt;
|Current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
For a module named &amp;lt;code&amp;gt;Example.pak&amp;lt;/code&amp;gt;, the following reads &amp;lt;code&amp;gt;Saves/Example/profile.txt&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The function returns &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt; when the requested file does not exist or cannot be opened. Always validate the result before using it:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/config.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;lt; 0) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== savefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, filename [, pathname [, save_type]] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Writes the current buffer to an external file. The destination is opened in write mode, so an existing file at the same path is replaced.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR writes to the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;An optional custom pathname is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot; );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The custom pathname is joined directly to the filename. Include the final slash or backslash in &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Default save paths are recommended for portable persistent data. Custom relative paths may resolve differently between platforms and launch environments.&lt;br /&gt;
&lt;br /&gt;
Text mode writes the buffer and then adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; to the end of the file. Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;save_type&amp;lt;/code&amp;gt; suppresses that final line break:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;output.dat&amp;quot;, &amp;quot;data/generated/&amp;quot;, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Text mode adds this line break unconditionally. If a loaded buffer already ends with &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; and is saved again without new content after it, the file gains another empty line.&lt;br /&gt;
&lt;br /&gt;
Saving does not close the filestream. The same buffer may be appended or saved again before it is released.&lt;br /&gt;
&lt;br /&gt;
=== setfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, position);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Sets the read position to a byte offset. Use &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to return to the beginning:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, 0);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The engine does not validate or clamp this value. Negative positions and positions beyond the allocated buffer are invalid and may cause unstable behavior.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not affect &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt;, which always writes at the buffer end.&lt;br /&gt;
&lt;br /&gt;
== Iterating Through Lines ==&lt;br /&gt;
OpenBOR does not provide a separate end-of-filestream function. Compare the position before and after &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; to detect when no further progress is possible:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/records.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    while (1) {&lt;br /&gt;
        int current_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        char current_line = getfilestreamline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        int next_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        if (current_line != &amp;quot;&amp;quot;) {&lt;br /&gt;
            log(current_line + &amp;quot;\n&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (next_position == current_position) {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This pattern also tolerates empty lines. &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; skips their line-ending characters and continues to the next populated line.&lt;br /&gt;
&lt;br /&gt;
== Binary Data ==&lt;br /&gt;
Byte mode lets scripts work with individual numeric byte values instead of parsed text. This is useful for compact records, custom encodings, byte-level inspection, and data containing non-printing values that are awkward to represent as ordinary strings. Reads return the byte at the current position, while append operations can add byte values directly without converting them to text.&lt;br /&gt;
&lt;br /&gt;
Byte mode is not currently a complete binary-file interface. Saving determines the output length by scanning for a null terminator, so a byte value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; ends the writable content. Values from &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;255&amp;lt;/code&amp;gt; can be manipulated and preserved, but files containing embedded null bytes cannot be saved reliably. Use byte mode for controlled byte-oriented formats, and text mode or another native API when arbitrary binary data must be preserved.&lt;br /&gt;
&lt;br /&gt;
== Practical Guidance ==&lt;br /&gt;
&lt;br /&gt;
* Check every &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; result for &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Keep handles private to the code that owns their lifecycle when practical.&lt;br /&gt;
* Maximum file path length is 511 characters. Be aware that some operating systems impose stricter limits. Windows 11, for example, limits file paths to 260 characters by default. For portability, try to keep file paths reasonable in length.&lt;br /&gt;
* Save before closing when changes must persist.&lt;br /&gt;
* Close every filestream when it is no longer needed.&lt;br /&gt;
* Never use a closed handle or invent a handle index.&lt;br /&gt;
* Check the return value from &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; before assuming a file was removed.&lt;br /&gt;
* Close a buffer separately when deleting the file from which it was loaded.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamposition()&amp;lt;/code&amp;gt; to preserve known-safe positions instead of guessing byte offsets.&lt;br /&gt;
* Prefer one logical record per line and whitespace-separated fields when using &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when field content may contain spaces.&lt;br /&gt;
* Avoid loading unnecessarily large files. Opening a filestream allocates enough memory for the complete file.&lt;br /&gt;
* Remember that packfile content is not modified in place. Save changed data to the module&#039;s save directory or another writable external path.&lt;br /&gt;
&lt;br /&gt;
The engine releases any remaining filestream buffers during shutdown, but does not save them automatically or delete their external files. Explicit cleanup keeps ownership clear and allows closed handle slots to be reused during play.&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
* [[:Category:Script Functions|Script Functions]]&lt;br /&gt;
* [[Arrays]]&lt;br /&gt;
* [[Strings]]&lt;br /&gt;
* [[Variables Overview]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Script]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1080</id>
		<title>File Operations</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1080"/>
		<updated>2026-08-24T05:30:58Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* getfilestreamargument() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;OpenBOR supports file CRUD - create, read, update, and delete - through an API called filestream. Scripts can load external files into memory, read structured text, assemble or revise content, save the result to disk, and remove files that are no longer needed. Common uses include custom configuration, persistent game data, generated model or script files, and data exchanged with external tools.&lt;br /&gt;
&lt;br /&gt;
Despite the name, an OpenBOR filestream is not a continuously open operating-system file. Instead, OpenBOR filestreams use memory-backed buffers. Opening a file loads its contents into memory, after which reading and editing require no additional disk access or filesystem overhead. Scripts may perform any number of operations at memory speed, then explicitly commit the finished buffer to storage with &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Each filestream maintains two independent locations:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Buffer end -&#039;&#039;&#039; &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt; always writes here.&lt;br /&gt;
* &#039;&#039;&#039;Read position -&#039;&#039;&#039; Line, argument, byte, and position functions operate here.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not change where appended data is written.&amp;lt;blockquote&amp;gt;&#039;&#039;&#039;Important:&#039;&#039;&#039; Memory buffers and external files have separate lifecycles. &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; releases a buffer without deleting its saved file. &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; removes an external file without releasing any buffer previously loaded from it.&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lifecycle ==&lt;br /&gt;
Creating, reading, or updating a file normally follows four steps:&lt;br /&gt;
&lt;br /&gt;
# Create an empty stream with &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt;, or load an existing file with &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Read from the current position or append content to the buffer.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; when the buffer should be written to disk.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; when the buffer is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Deletion is independent of this handle lifecycle. Pass a filename directly to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt;; no stream needs to be open.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Operation&lt;br /&gt;
!Native support&lt;br /&gt;
!Method&lt;br /&gt;
|-&lt;br /&gt;
|Create&lt;br /&gt;
|Yes&lt;br /&gt;
|Create an empty buffer, append content, then save it under a filename.&lt;br /&gt;
|-&lt;br /&gt;
|Read&lt;br /&gt;
|Yes&lt;br /&gt;
|Open a file, then read its current line, arguments, or byte.&lt;br /&gt;
|-&lt;br /&gt;
|Update&lt;br /&gt;
|Append or rewrite&lt;br /&gt;
|Open and append to the loaded buffer, or construct a replacement buffer, then save over the previous file.&lt;br /&gt;
|-&lt;br /&gt;
|Delete&lt;br /&gt;
|Yes&lt;br /&gt;
|Pass a filename to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; to remove a writable external file.&lt;br /&gt;
|}&lt;br /&gt;
Saving is explicit. Closing an unsaved stream discards its in-memory changes. Deleting a file does not discard a loaded buffer, so saving that buffer later may recreate the file.&lt;br /&gt;
&lt;br /&gt;
== Handle and Buffer Model ==&lt;br /&gt;
The returned filestream handle is a zero-based index into the engine&#039;s filestream collection. Internally, each entry contains:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Member&lt;br /&gt;
!Purpose&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;buf&amp;lt;/code&amp;gt;&lt;br /&gt;
|Allocated memory containing the loaded or constructed data.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;size&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current buffer size and append location.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;pos&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current byte position used by read functions.&lt;br /&gt;
|}&lt;br /&gt;
Closed entries become available for reuse. Once &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called, the old handle is invalid and may later identify a different filestream.&lt;br /&gt;
&lt;br /&gt;
The engine does not clamp filestream handles or manually assigned positions. Use only handles returned by &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt; or successful calls to &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;, and never use a handle after closing it.&lt;br /&gt;
&lt;br /&gt;
== Walk-through ==&lt;br /&gt;
&lt;br /&gt;
=== Create and Save ===&lt;br /&gt;
The following creates a two-line text file:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = createfilestream();&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;name&amp;quot;, 1);&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;Max&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;score&amp;quot;, 0);&lt;br /&gt;
filestreamappend(filestream_handle, 125000);&lt;br /&gt;
&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default save location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;. Text mode adds a final carriage return and line feed, producing:&amp;lt;pre&amp;gt;&lt;br /&gt;
name Max&lt;br /&gt;
score 125000&lt;br /&gt;
&amp;lt;/pre&amp;gt;In this example, write type &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; adds a trailing space after &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;. Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; begins a new line before &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; and then adds a trailing space.&lt;br /&gt;
&lt;br /&gt;
=== Open and Read ===&lt;br /&gt;
Pass a nonzero location value to load from the current module&#039;s save directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    char player_name = getfilestreamargument(filestream_handle, 1, &amp;quot;string&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
    int player_score = getfilestreamargument(filestream_handle, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The first argument on each line is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. The example reads index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, returning &amp;lt;code&amp;gt;&amp;quot;Max&amp;quot;&amp;lt;/code&amp;gt; from the first line and &amp;lt;code&amp;gt;125000&amp;lt;/code&amp;gt; from the second.&lt;br /&gt;
&lt;br /&gt;
=== Append and Replace ===&lt;br /&gt;
Opening a file loads its existing content into the buffer. Appending new content and saving under the same name rewrites the external file with the expanded buffer:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    /*&lt;br /&gt;
    * Files written by savefilestream() in text mode already&lt;br /&gt;
    * end with a line break, so begin the next record directly.&lt;br /&gt;
    */&lt;br /&gt;
    filestreamappend(filestream_handle, &amp;quot;unlocks&amp;quot;, 1);&lt;br /&gt;
    filestreamappend(filestream_handle, 3);&lt;br /&gt;
&lt;br /&gt;
    savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; opens the destination in write mode and replaces its previous contents. Existing data survives only because &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; first copied that data into the memory buffer.&lt;br /&gt;
&lt;br /&gt;
Filestreams do not provide insertion, replacement, or removal at an arbitrary read position. To modify existing records, read the source and assemble the desired result in another filestream, then save the replacement over the original filename.&lt;br /&gt;
&lt;br /&gt;
=== Delete ===&lt;br /&gt;
Deletion targets the external file by name rather than a filestream handle:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (!deleted) {&lt;br /&gt;
    log(&amp;quot;Could not delete profile.txt.\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;, matching &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. Supply the same optional pathname used to save a file when deleting from a custom location:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&lt;br /&gt;
    &amp;quot;profile.txt&amp;quot;,&lt;br /&gt;
    &amp;quot;data/generated/&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Only the external file is removed. If a filestream already contains data loaded from that file, its memory buffer and handle remain valid until &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
&lt;br /&gt;
=== closefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Releases the filestream&#039;s allocated buffer. The handle becomes invalid and its collection index may be reused by a later create or open operation.&lt;br /&gt;
&lt;br /&gt;
This function does not save the buffer and does not delete an external file.&lt;br /&gt;
&lt;br /&gt;
=== createfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = createfilestream();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Allocates an empty memory buffer and returns its handle. The initial read position and content size are both &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No external file is created until the buffer is passed to &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== deletefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(filename[, pathname]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Deletes a writable external file and returns &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; when the operating system confirms deletion. A return value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; means the file could not be deleted, including when the file does not exist, the path is incorrect, or the operating system denies access.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR targets the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The optional pathname follows the same rules as &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; and is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream( &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The pathname is joined directly to the filename. Include the final slash or backslash.&lt;br /&gt;
&lt;br /&gt;
Deletion operates on external files only and cannot remove content from a packfile. It does not accept a filestream handle, release a memory buffer, or invalidate a handle. Saving a still-open buffer to the same filename after successful deletion creates the file again.&lt;br /&gt;
&lt;br /&gt;
=== filestreamappend() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, value[, write_type[, value_type]]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Converts the supplied value to text and appends it at the end of the buffer. The current read position has no effect on the append location.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;write_type&amp;lt;/code&amp;gt;&lt;br /&gt;
!Behavior&lt;br /&gt;
|-&lt;br /&gt;
|Omitted, &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;, or any value other than &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value without adding a separator.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; before the value, then adds one trailing space after it.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value followed by one trailing space.&lt;br /&gt;
|}&lt;br /&gt;
Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; starts a new line before the supplied value. Using it for the first value in an empty stream therefore creates an initial blank line.&lt;br /&gt;
&lt;br /&gt;
Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;value_type&amp;lt;/code&amp;gt; selects the legacy byte append path:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, byte_value, write_type, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[File Operations#Binary Data|Binary Data]] before using byte mode.&lt;br /&gt;
&lt;br /&gt;
=== filestreamnextline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamnextline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Advances the read position from its current location to the next non-line-break byte. The function scans to the end of the current line, then skips all consecutive &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt; characters.&lt;br /&gt;
&lt;br /&gt;
Multiple empty lines are skipped as one group. At the end of the buffer, the position does not change.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamargument() ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
mixed result = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    result_type&lt;br /&gt;
    [, decode_quotes]&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reads an argument beginning at the current filestream position without advancing the position. Argument indexes are zero-based.&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter selects how arguments and quotation marks are interpreted. It preserves compatibility with modules written for the historical filestream parser while providing modern quote-aware parsing when requested.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt;&lt;br /&gt;
! Parsing behavior&lt;br /&gt;
|-&lt;br /&gt;
| Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| Legacy whitespace parsing. Spaces and tabs always separate arguments. Quotation marks are treated as ordinary characters and remain in the returned value.&lt;br /&gt;
|-&lt;br /&gt;
| Any nonzero value&lt;br /&gt;
| Modern quote-aware parsing. Matching quotes group text into one argument, and the surrounding quote delimiters are removed from the returned value.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Existing three-parameter calls retain the historical behavior:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pass a nonzero fourth parameter to request quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;,&lt;br /&gt;
    1&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;result_type&amp;lt;/code&amp;gt;&lt;br /&gt;
! Return&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument text.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to an integer.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to a decimal value.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unsigned value of the byte at the current read position. &amp;lt;code&amp;gt;argument_index&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; are ignored.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Result type names are case-insensitive.&lt;br /&gt;
&lt;br /&gt;
==== Legacy Parsing ====&lt;br /&gt;
&lt;br /&gt;
Legacy parsing is the default for compatibility with existing modules. Spaces and tabs always separate arguments, even when they appear between quotation marks. Quote characters remain part of the returned text.&lt;br /&gt;
&lt;br /&gt;
Given this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
msgid &amp;quot;How To Play&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following calls return:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Call&lt;br /&gt;
! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 0, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;msgid&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 1, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;How&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 2, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;To&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 3, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Play&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This behavior allows older scripts that read, process, and reconstruct text files one whitespace-delimited token at a time to continue working without modification.&lt;br /&gt;
&lt;br /&gt;
==== Quote-Aware Parsing ====&lt;br /&gt;
&lt;br /&gt;
Pass a nonzero fourth parameter to enable modern quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(file, 1, &amp;quot;string&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the same source line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
msgid &amp;quot;How To Play&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The call returns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
How To Play&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The complete quoted phrase occupies argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, and the surrounding quotation marks are removed.&lt;br /&gt;
&lt;br /&gt;
Outside quoted spans, spaces and tabs separate arguments, an unquoted line break ends the current record, and &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; begins a comment.&lt;br /&gt;
&lt;br /&gt;
===== Double Quotes =====&lt;br /&gt;
&lt;br /&gt;
Double quotes may group text containing whitespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name &amp;quot;Max Thunder&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char name = getfilestreamargument(file, 1, &amp;quot;string&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Max Thunder&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Single Quotes =====&lt;br /&gt;
&lt;br /&gt;
Single quotes may also group text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&#039;Multiple words&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Multiple words&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Mixed Quotes =====&lt;br /&gt;
&lt;br /&gt;
The opposite quote type remains literal inside a quoted span:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Numeric Results ====&lt;br /&gt;
&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate the file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
Quote handling occurs before numeric conversion when modern parsing is enabled. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
value &amp;quot;125&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int legacy_value = getfilestreamargument(file, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
int decoded_value = getfilestreamargument(file, 1, &amp;quot;int&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The legacy result is &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; because the returned token begins with a quotation mark. The quote-aware result is &amp;lt;code&amp;gt;125&amp;lt;/code&amp;gt; because the delimiters are removed before conversion.&lt;br /&gt;
&lt;br /&gt;
String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters. The limit applies to the value returned to the script.&lt;br /&gt;
* Omit &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; when compatibility with historical whitespace-token behavior is required.&lt;br /&gt;
* Pass &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; as the fourth &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt; parameter when quoted fields should be treated as complete arguments.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when the exact line, including quotation marks and spacing, must be preserved.&lt;br /&gt;
&lt;br /&gt;
=== String ===&lt;br /&gt;
Matching single or double quotes group text into one argument. Opening and closing quote delimiters are discarded from the returned value. String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters - about 36 Standard Academic Pages. The limit applies after quote delimiters are removed.&lt;br /&gt;
&lt;br /&gt;
==== Double Quotes ====&lt;br /&gt;
Enclose multiple word tokens in quotes to group as a single string.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello world&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Result: Hello world&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
&lt;br /&gt;
Result: prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Single Quotes ====&lt;br /&gt;
You may also enclose tokens with single quotes.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &#039;Its&#039;&lt;br /&gt;
&lt;br /&gt;
Result: Its&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: It&#039;s&lt;br /&gt;
&lt;br /&gt;
Result: It&#039;s&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Mixed Quotes ====&lt;br /&gt;
You may mix quote types to preserve quotations within the string. The opposite quote type remains literal inside a quoted span:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Result: Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Value: &#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
&lt;br /&gt;
Result: She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Numeric ===&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char line = getfilestreamline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns text from the current read position through the next carriage return, line feed, or end of buffer. The line-ending characters are not included.&lt;br /&gt;
&lt;br /&gt;
This function does not advance the read position. Repeated calls return the same line until &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt; changes the position.&lt;br /&gt;
&lt;br /&gt;
An empty line and the end of the buffer both return an empty string.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns the current read position as a byte offset from the beginning of the buffer. The value is not a line number.&lt;br /&gt;
&lt;br /&gt;
Positions may be stored and supplied later to &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== openfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(filename[, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Loads an existing file into memory, sets its read position to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, and returns the filestream handle. The complete file is loaded at once.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;location&amp;lt;/code&amp;gt;&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Module-relative path. OpenBOR checks loose files and the current packfile.&lt;br /&gt;
|-&lt;br /&gt;
|Any nonzero value&lt;br /&gt;
|Current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
For a module named &amp;lt;code&amp;gt;Example.pak&amp;lt;/code&amp;gt;, the following reads &amp;lt;code&amp;gt;Saves/Example/profile.txt&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The function returns &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt; when the requested file does not exist or cannot be opened. Always validate the result before using it:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/config.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;lt; 0) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== savefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, filename [, pathname [, save_type]] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Writes the current buffer to an external file. The destination is opened in write mode, so an existing file at the same path is replaced.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR writes to the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;An optional custom pathname is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot; );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The custom pathname is joined directly to the filename. Include the final slash or backslash in &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Default save paths are recommended for portable persistent data. Custom relative paths may resolve differently between platforms and launch environments.&lt;br /&gt;
&lt;br /&gt;
Text mode writes the buffer and then adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; to the end of the file. Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;save_type&amp;lt;/code&amp;gt; suppresses that final line break:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;output.dat&amp;quot;, &amp;quot;data/generated/&amp;quot;, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Text mode adds this line break unconditionally. If a loaded buffer already ends with &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; and is saved again without new content after it, the file gains another empty line.&lt;br /&gt;
&lt;br /&gt;
Saving does not close the filestream. The same buffer may be appended or saved again before it is released.&lt;br /&gt;
&lt;br /&gt;
=== setfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, position);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Sets the read position to a byte offset. Use &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to return to the beginning:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, 0);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The engine does not validate or clamp this value. Negative positions and positions beyond the allocated buffer are invalid and may cause unstable behavior.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not affect &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt;, which always writes at the buffer end.&lt;br /&gt;
&lt;br /&gt;
== Iterating Through Lines ==&lt;br /&gt;
OpenBOR does not provide a separate end-of-filestream function. Compare the position before and after &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; to detect when no further progress is possible:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/records.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    while (1) {&lt;br /&gt;
        int current_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        char current_line = getfilestreamline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        int next_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        if (current_line != &amp;quot;&amp;quot;) {&lt;br /&gt;
            log(current_line + &amp;quot;\n&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (next_position == current_position) {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This pattern also tolerates empty lines. &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; skips their line-ending characters and continues to the next populated line.&lt;br /&gt;
&lt;br /&gt;
== Binary Data ==&lt;br /&gt;
Byte mode lets scripts work with individual numeric byte values instead of parsed text. This is useful for compact records, custom encodings, byte-level inspection, and data containing non-printing values that are awkward to represent as ordinary strings. Reads return the byte at the current position, while append operations can add byte values directly without converting them to text.&lt;br /&gt;
&lt;br /&gt;
Byte mode is not currently a complete binary-file interface. Saving determines the output length by scanning for a null terminator, so a byte value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; ends the writable content. Values from &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;255&amp;lt;/code&amp;gt; can be manipulated and preserved, but files containing embedded null bytes cannot be saved reliably. Use byte mode for controlled byte-oriented formats, and text mode or another native API when arbitrary binary data must be preserved.&lt;br /&gt;
&lt;br /&gt;
== Practical Guidance ==&lt;br /&gt;
&lt;br /&gt;
* Check every &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; result for &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Keep handles private to the code that owns their lifecycle when practical.&lt;br /&gt;
* Maximum file path length is 511 characters. Be aware that some operating systems impose stricter limits. Windows 11, for example, limits file paths to 260 characters by default. For portability, try to keep file paths reasonable in length.&lt;br /&gt;
* Save before closing when changes must persist.&lt;br /&gt;
* Close every filestream when it is no longer needed.&lt;br /&gt;
* Never use a closed handle or invent a handle index.&lt;br /&gt;
* Check the return value from &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; before assuming a file was removed.&lt;br /&gt;
* Close a buffer separately when deleting the file from which it was loaded.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamposition()&amp;lt;/code&amp;gt; to preserve known-safe positions instead of guessing byte offsets.&lt;br /&gt;
* Prefer one logical record per line and whitespace-separated fields when using &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when field content may contain spaces.&lt;br /&gt;
* Avoid loading unnecessarily large files. Opening a filestream allocates enough memory for the complete file.&lt;br /&gt;
* Remember that packfile content is not modified in place. Save changed data to the module&#039;s save directory or another writable external path.&lt;br /&gt;
&lt;br /&gt;
The engine releases any remaining filestream buffers during shutdown, but does not save them automatically or delete their external files. Explicit cleanup keeps ownership clear and allows closed handle slots to be reused during play.&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
* [[:Category:Script Functions|Script Functions]]&lt;br /&gt;
* [[Arrays]]&lt;br /&gt;
* [[Strings]]&lt;br /&gt;
* [[Variables Overview]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Script]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1079</id>
		<title>File Operations</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=File_Operations&amp;diff=1079"/>
		<updated>2026-08-24T05:08:16Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* getfilestreamargument() */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;OpenBOR supports file CRUD - create, read, update, and delete - through an API called filestream. Scripts can load external files into memory, read structured text, assemble or revise content, save the result to disk, and remove files that are no longer needed. Common uses include custom configuration, persistent game data, generated model or script files, and data exchanged with external tools.&lt;br /&gt;
&lt;br /&gt;
Despite the name, an OpenBOR filestream is not a continuously open operating-system file. Instead, OpenBOR filestreams use memory-backed buffers. Opening a file loads its contents into memory, after which reading and editing require no additional disk access or filesystem overhead. Scripts may perform any number of operations at memory speed, then explicitly commit the finished buffer to storage with &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
Each filestream maintains two independent locations:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Buffer end -&#039;&#039;&#039; &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt; always writes here.&lt;br /&gt;
* &#039;&#039;&#039;Read position -&#039;&#039;&#039; Line, argument, byte, and position functions operate here.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not change where appended data is written.&amp;lt;blockquote&amp;gt;&#039;&#039;&#039;Important:&#039;&#039;&#039; Memory buffers and external files have separate lifecycles. &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; releases a buffer without deleting its saved file. &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; removes an external file without releasing any buffer previously loaded from it.&amp;lt;/blockquote&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Lifecycle ==&lt;br /&gt;
Creating, reading, or updating a file normally follows four steps:&lt;br /&gt;
&lt;br /&gt;
# Create an empty stream with &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt;, or load an existing file with &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
# Read from the current position or append content to the buffer.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; when the buffer should be written to disk.&lt;br /&gt;
# Call &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; when the buffer is no longer needed.&lt;br /&gt;
&lt;br /&gt;
Deletion is independent of this handle lifecycle. Pass a filename directly to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt;; no stream needs to be open.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Operation&lt;br /&gt;
!Native support&lt;br /&gt;
!Method&lt;br /&gt;
|-&lt;br /&gt;
|Create&lt;br /&gt;
|Yes&lt;br /&gt;
|Create an empty buffer, append content, then save it under a filename.&lt;br /&gt;
|-&lt;br /&gt;
|Read&lt;br /&gt;
|Yes&lt;br /&gt;
|Open a file, then read its current line, arguments, or byte.&lt;br /&gt;
|-&lt;br /&gt;
|Update&lt;br /&gt;
|Append or rewrite&lt;br /&gt;
|Open and append to the loaded buffer, or construct a replacement buffer, then save over the previous file.&lt;br /&gt;
|-&lt;br /&gt;
|Delete&lt;br /&gt;
|Yes&lt;br /&gt;
|Pass a filename to &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; to remove a writable external file.&lt;br /&gt;
|}&lt;br /&gt;
Saving is explicit. Closing an unsaved stream discards its in-memory changes. Deleting a file does not discard a loaded buffer, so saving that buffer later may recreate the file.&lt;br /&gt;
&lt;br /&gt;
== Handle and Buffer Model ==&lt;br /&gt;
The returned filestream handle is a zero-based index into the engine&#039;s filestream collection. Internally, each entry contains:&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!Member&lt;br /&gt;
!Purpose&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;buf&amp;lt;/code&amp;gt;&lt;br /&gt;
|Allocated memory containing the loaded or constructed data.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;size&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current buffer size and append location.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;pos&amp;lt;/code&amp;gt;&lt;br /&gt;
|Current byte position used by read functions.&lt;br /&gt;
|}&lt;br /&gt;
Closed entries become available for reuse. Once &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called, the old handle is invalid and may later identify a different filestream.&lt;br /&gt;
&lt;br /&gt;
The engine does not clamp filestream handles or manually assigned positions. Use only handles returned by &amp;lt;code&amp;gt;createfilestream()&amp;lt;/code&amp;gt; or successful calls to &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt;, and never use a handle after closing it.&lt;br /&gt;
&lt;br /&gt;
== Walk-through ==&lt;br /&gt;
&lt;br /&gt;
=== Create and Save ===&lt;br /&gt;
The following creates a two-line text file:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = createfilestream();&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;name&amp;quot;, 1);&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;Max&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
filestreamappend(filestream_handle, &amp;quot;score&amp;quot;, 0);&lt;br /&gt;
filestreamappend(filestream_handle, 125000);&lt;br /&gt;
&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default save location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;. Text mode adds a final carriage return and line feed, producing:&amp;lt;pre&amp;gt;&lt;br /&gt;
name Max&lt;br /&gt;
score 125000&lt;br /&gt;
&amp;lt;/pre&amp;gt;In this example, write type &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; adds a trailing space after &amp;lt;code&amp;gt;name&amp;lt;/code&amp;gt;. Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; begins a new line before &amp;lt;code&amp;gt;score&amp;lt;/code&amp;gt; and then adds a trailing space.&lt;br /&gt;
&lt;br /&gt;
=== Open and Read ===&lt;br /&gt;
Pass a nonzero location value to load from the current module&#039;s save directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    char player_name = getfilestreamargument(filestream_handle, 1, &amp;quot;string&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
    int player_score = getfilestreamargument(filestream_handle, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The first argument on each line is index &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. The example reads index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, returning &amp;lt;code&amp;gt;&amp;quot;Max&amp;quot;&amp;lt;/code&amp;gt; from the first line and &amp;lt;code&amp;gt;125000&amp;lt;/code&amp;gt; from the second.&lt;br /&gt;
&lt;br /&gt;
=== Append and Replace ===&lt;br /&gt;
Opening a file loads its existing content into the buffer. Appending new content and saving under the same name rewrites the external file with the expanded buffer:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    /*&lt;br /&gt;
    * Files written by savefilestream() in text mode already&lt;br /&gt;
    * end with a line break, so begin the next record directly.&lt;br /&gt;
    */&lt;br /&gt;
    filestreamappend(filestream_handle, &amp;quot;unlocks&amp;quot;, 1);&lt;br /&gt;
    filestreamappend(filestream_handle, 3);&lt;br /&gt;
&lt;br /&gt;
    savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; opens the destination in write mode and replaces its previous contents. Existing data survives only because &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; first copied that data into the memory buffer.&lt;br /&gt;
&lt;br /&gt;
Filestreams do not provide insertion, replacement, or removal at an arbitrary read position. To modify existing records, read the source and assemble the desired result in another filestream, then save the replacement over the original filename.&lt;br /&gt;
&lt;br /&gt;
=== Delete ===&lt;br /&gt;
Deletion targets the external file by name rather than a filestream handle:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (!deleted) {&lt;br /&gt;
    log(&amp;quot;Could not delete profile.txt.\n&amp;quot;);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The default location is the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;, matching &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;. Supply the same optional pathname used to save a file when deleting from a custom location:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&lt;br /&gt;
    &amp;quot;profile.txt&amp;quot;,&lt;br /&gt;
    &amp;quot;data/generated/&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Only the external file is removed. If a filestream already contains data loaded from that file, its memory buffer and handle remain valid until &amp;lt;code&amp;gt;closefilestream()&amp;lt;/code&amp;gt; is called.&lt;br /&gt;
&lt;br /&gt;
== Functions ==&lt;br /&gt;
&lt;br /&gt;
=== closefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
closefilestream(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Releases the filestream&#039;s allocated buffer. The handle becomes invalid and its collection index may be reused by a later create or open operation.&lt;br /&gt;
&lt;br /&gt;
This function does not save the buffer and does not delete an external file.&lt;br /&gt;
&lt;br /&gt;
=== createfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = createfilestream();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Allocates an empty memory buffer and returns its handle. The initial read position and content size are both &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
No external file is created until the buffer is passed to &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== deletefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(filename[, pathname]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Deletes a writable external file and returns &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; when the operating system confirms deletion. A return value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; means the file could not be deleted, including when the file does not exist, the path is incorrect, or the operating system denies access.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR targets the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream(&amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The optional pathname follows the same rules as &amp;lt;code&amp;gt;savefilestream()&amp;lt;/code&amp;gt; and is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int deleted = deletefilestream( &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The pathname is joined directly to the filename. Include the final slash or backslash.&lt;br /&gt;
&lt;br /&gt;
Deletion operates on external files only and cannot remove content from a packfile. It does not accept a filestream handle, release a memory buffer, or invalidate a handle. Saving a still-open buffer to the same filename after successful deletion creates the file again.&lt;br /&gt;
&lt;br /&gt;
=== filestreamappend() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, value[, write_type[, value_type]]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Converts the supplied value to text and appends it at the end of the buffer. The current read position has no effect on the append location.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;write_type&amp;lt;/code&amp;gt;&lt;br /&gt;
!Behavior&lt;br /&gt;
|-&lt;br /&gt;
|Omitted, &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;, or any value other than &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value without adding a separator.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; before the value, then adds one trailing space after it.&lt;br /&gt;
|-&lt;br /&gt;
|&amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;&lt;br /&gt;
|Appends the value followed by one trailing space.&lt;br /&gt;
|}&lt;br /&gt;
Write type &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; starts a new line before the supplied value. Using it for the first value in an empty stream therefore creates an initial blank line.&lt;br /&gt;
&lt;br /&gt;
Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;value_type&amp;lt;/code&amp;gt; selects the legacy byte append path:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamappend(filestream_handle, byte_value, write_type, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;See [[File Operations#Binary Data|Binary Data]] before using byte mode.&lt;br /&gt;
&lt;br /&gt;
=== filestreamnextline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
filestreamnextline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Advances the read position from its current location to the next non-line-break byte. The function scans to the end of the current line, then skips all consecutive &amp;lt;code&amp;gt;\r&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;\n&amp;lt;/code&amp;gt; characters.&lt;br /&gt;
&lt;br /&gt;
Multiple empty lines are skipped as one group. At the end of the buffer, the position does not change.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamargument() ===&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
mixed result = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    result_type&lt;br /&gt;
    [, decode_quotes]&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Reads an argument beginning at the current filestream position without advancing the position. Argument indexes are zero-based.&lt;br /&gt;
&lt;br /&gt;
The optional &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; parameter selects how arguments and quotation marks are interpreted. It preserves compatibility with modules written for the historical filestream parser while providing modern quote-aware parsing when requested.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt;&lt;br /&gt;
! Parsing behavior&lt;br /&gt;
|-&lt;br /&gt;
| Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
| Legacy whitespace parsing. Spaces and tabs always separate arguments. Quotation marks are treated as ordinary characters and remain in the returned value.&lt;br /&gt;
|-&lt;br /&gt;
| Any nonzero value&lt;br /&gt;
| Modern quote-aware parsing. Matching quotes group text into one argument, and the surrounding quote delimiters are removed from the returned value.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Existing three-parameter calls retain the historical behavior:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Pass a nonzero fourth parameter to request quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(&lt;br /&gt;
    filestream_handle,&lt;br /&gt;
    argument_index,&lt;br /&gt;
    &amp;quot;string&amp;quot;,&lt;br /&gt;
    1&lt;br /&gt;
);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! &amp;lt;code&amp;gt;result_type&amp;lt;/code&amp;gt;&lt;br /&gt;
! Return&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;string&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument text.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to an integer.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Argument converted to a decimal value.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
| Unsigned value of the byte at the current read position. &amp;lt;code&amp;gt;argument_index&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; are ignored.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Result type names are case-insensitive.&lt;br /&gt;
&lt;br /&gt;
==== Legacy Parsing ====&lt;br /&gt;
&lt;br /&gt;
Legacy parsing is the default for compatibility with existing modules. Spaces and tabs always separate arguments, even when they appear between quotation marks. Quote characters remain part of the returned text.&lt;br /&gt;
&lt;br /&gt;
Given this line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
msgid &amp;quot;How To Play&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The following calls return:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Call&lt;br /&gt;
! Result&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 0, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;msgid&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 1, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;quot;How&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 2, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;To&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;getfilestreamargument(file, 3, &amp;quot;string&amp;quot;)&amp;lt;/code&amp;gt;&lt;br /&gt;
| &amp;lt;code&amp;gt;Play&amp;quot;&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
This behavior allows older scripts that read, process, and reconstruct text files one whitespace-delimited token at a time to continue working without modification.&lt;br /&gt;
&lt;br /&gt;
==== Quote-Aware Parsing ====&lt;br /&gt;
&lt;br /&gt;
Pass a nonzero fourth parameter to enable modern quote-aware parsing:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char value = getfilestreamargument(file, 1, &amp;quot;string&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Using the same source line:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
msgid &amp;quot;How To Play&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The call returns:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
How To Play&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The complete quoted phrase occupies argument index &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt;, and the surrounding quotation marks are removed.&lt;br /&gt;
&lt;br /&gt;
Outside quoted spans, spaces and tabs separate arguments, an unquoted line break ends the current record, and &amp;lt;code&amp;gt;#&amp;lt;/code&amp;gt; begins a comment.&lt;br /&gt;
&lt;br /&gt;
===== Double Quotes =====&lt;br /&gt;
&lt;br /&gt;
Double quotes may group text containing whitespace:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
name &amp;quot;Max Thunder&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char name = getfilestreamargument(file, 1, &amp;quot;string&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Max Thunder&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Single Quotes =====&lt;br /&gt;
&lt;br /&gt;
Single quotes may also group text:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&#039;Multiple words&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Multiple words&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
It&#039;s&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
===== Mixed Quotes =====&lt;br /&gt;
&lt;br /&gt;
The opposite quote type remains literal inside a quoted span:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
&#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Result:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Numeric Results ====&lt;br /&gt;
&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate the file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
Quote handling occurs before numeric conversion when modern parsing is enabled. For example:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;pre&amp;gt;&lt;br /&gt;
value &amp;quot;125&amp;quot;&lt;br /&gt;
&amp;lt;/pre&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int legacy_value = getfilestreamargument(file, 1, &amp;quot;int&amp;quot;);&lt;br /&gt;
int decoded_value = getfilestreamargument(file, 1, &amp;quot;int&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The legacy result is &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; because the returned token begins with a quotation mark. The quote-aware result is &amp;lt;code&amp;gt;125&amp;lt;/code&amp;gt; because the delimiters are removed before conversion.&lt;br /&gt;
&lt;br /&gt;
String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters. The limit applies to the value returned to the script.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;!-- Replace the existing getfilestreamargument() guidance bullets under Practical Guidance with the following. --&amp;gt;&lt;br /&gt;
&lt;br /&gt;
* Omit &amp;lt;code&amp;gt;decode_quotes&amp;lt;/code&amp;gt; when compatibility with historical whitespace-token behavior is required.&lt;br /&gt;
* Pass &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; as the fourth &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt; parameter when quoted fields should be treated as complete arguments.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when the exact line, including quotation marks and spacing, must be preserved.&lt;br /&gt;
&lt;br /&gt;
=== String ===&lt;br /&gt;
Matching single or double quotes group text into one argument. Opening and closing quote delimiters are discarded from the returned value. String results may contain up to &amp;lt;code&amp;gt;MAX_SCRIPT_STRING_LENGTH&amp;lt;/code&amp;gt;, currently 65,535 characters - about 36 Standard Academic Pages. The limit applies after quote delimiters are removed.&lt;br /&gt;
&lt;br /&gt;
==== Double Quotes ====&lt;br /&gt;
Enclose multiple word tokens in quotes to group as a single string.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello world&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Result: Hello world&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Double-quoted spans may appear within an argument and are concatenated with adjacent unquoted text:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: prefix&amp;quot;two words&amp;quot;suffix&lt;br /&gt;
&lt;br /&gt;
Result: prefixtwo wordssuffix&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Single Quotes ====&lt;br /&gt;
You may also enclose tokens with single quotes.&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &#039;Its&#039;&lt;br /&gt;
&lt;br /&gt;
Result: Its&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Single quotes begin grouping only at an argument boundary. This preserves apostrophes in ordinary words:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: It&#039;s&lt;br /&gt;
&lt;br /&gt;
Result: It&#039;s&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==== Mixed Quotes ====&lt;br /&gt;
You may mix quote types to preserve quotations within the string. The opposite quote type remains literal inside a quoted span:&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
Value: &amp;quot;Hello, it&#039;s a good day.&amp;quot;&lt;br /&gt;
&lt;br /&gt;
Result: Hello, it&#039;s a good day.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&amp;lt;syntaxhighlight&amp;gt;&lt;br /&gt;
Value: &#039;She said &amp;quot;hello&amp;quot;.&#039;&lt;br /&gt;
&lt;br /&gt;
Result: She said &amp;quot;hello&amp;quot;.&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== Numeric ===&lt;br /&gt;
Missing or nonnumeric arguments converted with &amp;lt;code&amp;gt;&amp;quot;int&amp;quot;&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;&amp;quot;float&amp;quot;&amp;lt;/code&amp;gt; resolve to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;. Validate file structure when zero is also a meaningful value.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamline() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
char line = getfilestreamline(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns text from the current read position through the next carriage return, line feed, or end of buffer. The line-ending characters are not included.&lt;br /&gt;
&lt;br /&gt;
This function does not advance the read position. Repeated calls return the same line until &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt; changes the position.&lt;br /&gt;
&lt;br /&gt;
An empty line and the end of the buffer both return an empty string.&lt;br /&gt;
&lt;br /&gt;
=== getfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Returns the current read position as a byte offset from the beginning of the buffer. The value is not a line number.&lt;br /&gt;
&lt;br /&gt;
Positions may be stored and supplied later to &amp;lt;code&amp;gt;setfilestreamposition()&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
=== openfilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(filename[, location]);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Loads an existing file into memory, sets its read position to &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;, and returns the filestream handle. The complete file is loaded at once.&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
!&amp;lt;code&amp;gt;location&amp;lt;/code&amp;gt;&lt;br /&gt;
!Source&lt;br /&gt;
|-&lt;br /&gt;
|Omitted or &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt;&lt;br /&gt;
|Module-relative path. OpenBOR checks loose files and the current packfile.&lt;br /&gt;
|-&lt;br /&gt;
|Any nonzero value&lt;br /&gt;
|Current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;.&lt;br /&gt;
|}&lt;br /&gt;
For a module named &amp;lt;code&amp;gt;Example.pak&amp;lt;/code&amp;gt;, the following reads &amp;lt;code&amp;gt;Saves/Example/profile.txt&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
int filestream_handle = openfilestream(&amp;quot;profile.txt&amp;quot;, 1);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
The function returns &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt; when the requested file does not exist or cannot be opened. Always validate the result before using it:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/config.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;lt; 0) {&lt;br /&gt;
    return;&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=== savefilestream() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, filename [, pathname [, save_type]] );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Writes the current buffer to an external file. The destination is opened in write mode, so an existing file at the same path is replaced.&lt;br /&gt;
&lt;br /&gt;
When &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt; is omitted, OpenBOR writes to the current module&#039;s subdirectory under &amp;lt;code&amp;gt;Saves&amp;lt;/code&amp;gt;:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;profile.txt&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;An optional custom pathname is relative to the engine&#039;s working directory:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream( filestream_handle, &amp;quot;profile.txt&amp;quot;, &amp;quot;data/generated/&amp;quot; );&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The custom pathname is joined directly to the filename. Include the final slash or backslash in &amp;lt;code&amp;gt;pathname&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
Default save paths are recommended for portable persistent data. Custom relative paths may resolve differently between platforms and launch environments.&lt;br /&gt;
&lt;br /&gt;
Text mode writes the buffer and then adds &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; to the end of the file. Passing &amp;lt;code&amp;gt;&amp;quot;byte&amp;quot;&amp;lt;/code&amp;gt; as &amp;lt;code&amp;gt;save_type&amp;lt;/code&amp;gt; suppresses that final line break:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
savefilestream(filestream_handle, &amp;quot;output.dat&amp;quot;, &amp;quot;data/generated/&amp;quot;, &amp;quot;byte&amp;quot;);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Text mode adds this line break unconditionally. If a loaded buffer already ends with &amp;lt;code&amp;gt;\r\n&amp;lt;/code&amp;gt; and is saved again without new content after it, the file gains another empty line.&lt;br /&gt;
&lt;br /&gt;
Saving does not close the filestream. The same buffer may be appended or saved again before it is released.&lt;br /&gt;
&lt;br /&gt;
=== setfilestreamposition() ===&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, position);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;Sets the read position to a byte offset. Use &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; to return to the beginning:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot;&amp;gt;&lt;br /&gt;
setfilestreamposition(filestream_handle, 0);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;The engine does not validate or clamp this value. Negative positions and positions beyond the allocated buffer are invalid and may cause unstable behavior.&lt;br /&gt;
&lt;br /&gt;
Changing the read position does not affect &amp;lt;code&amp;gt;filestreamappend()&amp;lt;/code&amp;gt;, which always writes at the buffer end.&lt;br /&gt;
&lt;br /&gt;
== Iterating Through Lines ==&lt;br /&gt;
OpenBOR does not provide a separate end-of-filestream function. Compare the position before and after &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; to detect when no further progress is possible:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void filestream_handle = openfilestream(&amp;quot;data/records.txt&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
if (filestream_handle &amp;gt;= 0) {&lt;br /&gt;
    while (1) {&lt;br /&gt;
        int current_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        char current_line = getfilestreamline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        filestreamnextline(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        int next_position = getfilestreamposition(filestream_handle);&lt;br /&gt;
&lt;br /&gt;
        if (current_line != &amp;quot;&amp;quot;) {&lt;br /&gt;
            log(current_line + &amp;quot;\n&amp;quot;);&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        if (next_position == current_position) {&lt;br /&gt;
            break;&lt;br /&gt;
        }&lt;br /&gt;
    }&lt;br /&gt;
&lt;br /&gt;
    closefilestream(filestream_handle);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;This pattern also tolerates empty lines. &amp;lt;code&amp;gt;filestreamnextline()&amp;lt;/code&amp;gt; skips their line-ending characters and continues to the next populated line.&lt;br /&gt;
&lt;br /&gt;
== Binary Data ==&lt;br /&gt;
Byte mode lets scripts work with individual numeric byte values instead of parsed text. This is useful for compact records, custom encodings, byte-level inspection, and data containing non-printing values that are awkward to represent as ordinary strings. Reads return the byte at the current position, while append operations can add byte values directly without converting them to text.&lt;br /&gt;
&lt;br /&gt;
Byte mode is not currently a complete binary-file interface. Saving determines the output length by scanning for a null terminator, so a byte value of &amp;lt;code&amp;gt;0&amp;lt;/code&amp;gt; ends the writable content. Values from &amp;lt;code&amp;gt;1&amp;lt;/code&amp;gt; through &amp;lt;code&amp;gt;255&amp;lt;/code&amp;gt; can be manipulated and preserved, but files containing embedded null bytes cannot be saved reliably. Use byte mode for controlled byte-oriented formats, and text mode or another native API when arbitrary binary data must be preserved.&lt;br /&gt;
&lt;br /&gt;
== Practical Guidance ==&lt;br /&gt;
&lt;br /&gt;
* Check every &amp;lt;code&amp;gt;openfilestream()&amp;lt;/code&amp;gt; result for &amp;lt;code&amp;gt;-1&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Keep handles private to the code that owns their lifecycle when practical.&lt;br /&gt;
* Maximum file path length is 511 characters. Be aware that some operating systems impose stricter limits. Windows 11, for example, limits file paths to 260 characters by default. For portability, try to keep file paths reasonable in length.&lt;br /&gt;
* Save before closing when changes must persist.&lt;br /&gt;
* Close every filestream when it is no longer needed.&lt;br /&gt;
* Never use a closed handle or invent a handle index.&lt;br /&gt;
* Check the return value from &amp;lt;code&amp;gt;deletefilestream()&amp;lt;/code&amp;gt; before assuming a file was removed.&lt;br /&gt;
* Close a buffer separately when deleting the file from which it was loaded.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamposition()&amp;lt;/code&amp;gt; to preserve known-safe positions instead of guessing byte offsets.&lt;br /&gt;
* Prefer one logical record per line and whitespace-separated fields when using &amp;lt;code&amp;gt;getfilestreamargument()&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Use &amp;lt;code&amp;gt;getfilestreamline()&amp;lt;/code&amp;gt; when field content may contain spaces.&lt;br /&gt;
* Avoid loading unnecessarily large files. Opening a filestream allocates enough memory for the complete file.&lt;br /&gt;
* Remember that packfile content is not modified in place. Save changed data to the module&#039;s save directory or another writable external path.&lt;br /&gt;
&lt;br /&gt;
The engine releases any remaining filestream buffers during shutdown, but does not save them automatically or delete their external files. Explicit cleanup keeps ownership clear and allows closed handle slots to be reused during play.&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
* [[:Category:Script Functions|Script Functions]]&lt;br /&gt;
* [[Arrays]]&lt;br /&gt;
* [[Strings]]&lt;br /&gt;
* [[Variables Overview]]&lt;br /&gt;
&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Script]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Script_Overview&amp;diff=1078</id>
		<title>Script Overview</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Script_Overview&amp;diff=1078"/>
		<updated>2026-08-23T19:11:55Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Script Entry Points */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
OpenBOR includes a C-based scripting engine that allows creators to build customized or all new functionality into their games. Scripts may execute in response to a wide range of engine events, including animation-frame updates, engine cycles, timer ticks, collisions, damage, key input, and many others. Creators can implement scripts through native define-file paths, user-defined libraries, or code inserted directly into level files and model sheets.&lt;br /&gt;
&lt;br /&gt;
Alongside its native function library, the OpenBOR scripting engine supports the &amp;lt;code&amp;gt;#include&amp;lt;/code&amp;gt; directive. An optional, memory-efficient &amp;lt;code&amp;gt;#import&amp;lt;/code&amp;gt; directive is also available when only function references are needed. These directives allow creators to build reusable custom libraries, then call their functions from any appropriate script event.&lt;br /&gt;
&lt;br /&gt;
Scripts can also perform create, read, update, and delete operations on external data files at runtime. This includes working with content such as model sheets, configuration data, and even other script files.&lt;br /&gt;
&lt;br /&gt;
In practical terms, OpenBOR places virtually no limit on the game mechanics creators can implement. If you can imagine it and write the logic, you can build it.&lt;br /&gt;
&lt;br /&gt;
== Basics ==&lt;br /&gt;
&lt;br /&gt;
=== Syntax ===&lt;br /&gt;
OpenBOR Script uses syntax similar to C, with several notable differences:&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Weak typing:&#039;&#039;&#039; OpenBOR Script is weakly typed. Variables are stored as variants, and the engine changes their types as needed. Type prefixes indicate the intended data type, but do not enforce it by default. Functions declared as void may still return values. Strong typing may optionally be enforced with type guards. See Variables and Type Guards for details.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Preprocessor directives:&#039;&#039;&#039; Standard C preprocessor directives such as &amp;lt;code&amp;gt;#include&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;#define&amp;lt;/code&amp;gt;, and &amp;lt;code&amp;gt;#ifdef&amp;lt;/code&amp;gt; are available. OpenBOR Script also provides an additional custom memory saving &amp;lt;code&amp;gt;#import&amp;lt;/code&amp;gt; directive implementation.&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;String concatenation:&#039;&#039;&#039; The &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt; operator may be used to combine strings:&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void string_a = &amp;quot;Hello &amp;quot;&lt;br /&gt;
void string_b = &amp;quot;world!&amp;quot;&lt;br /&gt;
&lt;br /&gt;
void string_c = string_a + string_b;&lt;br /&gt;
log(string_c); //&amp;quot;Hello world!&amp;quot;&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&#039;&#039;&#039;Assignment:&#039;&#039;&#039; During assignment, both the value and type of the right-hand variant are copied to the left-hand variant. No automatic type validation is performed, so creators should take care when assigning values between variables.&lt;br /&gt;
&lt;br /&gt;
=== Operators ===&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Category&lt;br /&gt;
! Operators&lt;br /&gt;
|-&lt;br /&gt;
| Arithmetic&lt;br /&gt;
| &amp;lt;code&amp;gt;+&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;-&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;%&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Increment and decrement&lt;br /&gt;
| &amp;lt;code&amp;gt;++&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;--&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Assignment&lt;br /&gt;
| &amp;lt;code&amp;gt;=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;+=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;-=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;*=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;/=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;%=&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Comparison&lt;br /&gt;
| &amp;lt;code&amp;gt;==&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;!=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;gt;=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;=&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Logical&lt;br /&gt;
| &amp;lt;code&amp;gt;!&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;amp;&amp;amp;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;||&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Bitwise&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;amp;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;|&amp;lt;code&amp;gt;&amp;amp;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;|&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;^&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;~&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;&amp;lt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;gt;&amp;gt;&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Bitwise assignment&lt;br /&gt;
| &amp;lt;code&amp;gt;&amp;amp;=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;|&amp;lt;code&amp;gt;&amp;amp;=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;nowiki&amp;gt;|=&amp;lt;/nowiki&amp;gt;&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;^=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;lt;&amp;lt;=&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;&amp;gt;&amp;gt;=&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Conditional&lt;br /&gt;
| &amp;lt;code&amp;gt;?:&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| Grouping and access&lt;br /&gt;
| &amp;lt;code&amp;gt;()&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;.&amp;lt;/code&amp;gt;&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
=== Control Flow ===&lt;br /&gt;
&lt;br /&gt;
* do&lt;br /&gt;
* while&lt;br /&gt;
* for&lt;br /&gt;
* break&lt;br /&gt;
* if&lt;br /&gt;
* else&lt;br /&gt;
* continue&lt;br /&gt;
* switch&lt;br /&gt;
* case&lt;br /&gt;
* return&lt;br /&gt;
=== Preprocessor Directives ===&lt;br /&gt;
&lt;br /&gt;
* #define&lt;br /&gt;
* #ifdef&lt;br /&gt;
* #ifndef&lt;br /&gt;
* #endif&lt;br /&gt;
* #include&lt;br /&gt;
* #import&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;#import&amp;lt;/code&amp;gt; is an OpenBOR-specific directive. Unlike &amp;lt;code&amp;gt;#include&amp;lt;/code&amp;gt;, which copies the contents of a file into the current script, &amp;lt;code&amp;gt;#import&amp;lt;/code&amp;gt; imports references to the file&#039;s functions without copying its contents. This can significantly reduce memory usage when sharing reusable function libraries across multiple scripts. &lt;br /&gt;
&lt;br /&gt;
=== Native Functions ===&lt;br /&gt;
In addition to the operators described above, OpenBOR provides a suite of [[:Category:Script Functions|native functions]] that give scripts direct control over engine primitives and systems. These functions allow creators to inspect, modify, replace, or extend engine behavior while also assisting with game logic, graphics generation, mathematical calculations, data handling, and other common scripting tasks.&lt;br /&gt;
&lt;br /&gt;
== Script Entry Points ==&lt;br /&gt;
Every OpenBOR script except for [[Animationscript|animation]] contains one required entry-point function, &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt;, and may also define two optional lifecycle functions, &amp;lt;code&amp;gt;oncreate()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;ondestroy()&amp;lt;/code&amp;gt;. &lt;br /&gt;
&lt;br /&gt;
=== main() ===&lt;br /&gt;
The engine executes &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; whenever the script’s associated event fires. Before execution, the engine automatically populates a collection of local variables containing information relevant to that event. Available variables depend on the script type and triggering event. For example, a collision script may receive references to the participating entities, while an input script may receive information about the player and activated controls.&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;void main() {&lt;br /&gt;
    // Do stuff here.&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== oncreate() ===&lt;br /&gt;
The engine executes &amp;lt;code&amp;gt;oncreate()&amp;lt;/code&amp;gt; when the script instance is created. This function may be used to initialize variables, allocate resources, or perform other setup required before the script begins responding to events.&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;void oncreate() {&lt;br /&gt;
    // Do stuff here.&lt;br /&gt;
}&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=== ondestroy() ===&lt;br /&gt;
The engine executes &amp;lt;code&amp;gt;ondestroy()&amp;lt;/code&amp;gt; when the script instance is destroyed. This function may be used to release resources, save state, or perform other cleanup associated with the script instance.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line=&amp;quot;1&amp;quot;&amp;gt;&lt;br /&gt;
void ondestroy() {&lt;br /&gt;
    // Do stuff here.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
[[Category:Openbor]]&lt;br /&gt;
[[Category:Script]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1077</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1077"/>
		<updated>2026-08-22T04:02:12Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how far it extends in both directions. Creators do not need to build their own asset handling, controls, movement, artificial intelligence, collision and combat logic, audio playback, or other foundational systems. Everything is integrated and ready to use. Gather the assets and a fully functional game can be running within hours.&lt;br /&gt;
&lt;br /&gt;
At the opposite extreme, OpenBOR exposes low-level primitives and engine objects through configuration and script. Creators can inspect runtime state, customize selected behavior, replace complete subsystems, or build new ones without modifying engine source or depending on third-party plugins. Native functionality is therefore a starting point, not a boundary.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and [[:Category:Script Events|more than 70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance&#039;&#039;&#039; - Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1076</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1076"/>
		<updated>2026-08-22T04:01:06Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Why OpenBOR? */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how far it extends in both directions. Creators do not need to build their own asset handling, controls, movement, artificial intelligence, collision and combat logic, audio playback, or other foundational systems. Everything is integrated and ready to use. Gather the assets and a fully functional game can be running within hours.&lt;br /&gt;
&lt;br /&gt;
At the opposite extreme, OpenBOR exposes low-level primitives and engine objects through configuration and script. Creators can inspect runtime state, customize selected behavior, replace complete subsystems, or build new ones without modifying engine source or depending on third-party plugins. Native functionality is therefore a starting point, not a boundary.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and over [[:Category:Script Events|70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance -&#039;&#039;&#039; Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter-smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1075</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1075"/>
		<updated>2026-08-22T03:59:54Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how far it extends in both directions. Creators do not need to build their own asset handling, controls, movement, artificial intelligence, collision and combat logic, audio playback, or other foundational systems. Everything is integrated and ready to use. Gather the assets and a fully functional game can be running within hours.&lt;br /&gt;
&lt;br /&gt;
At the opposite extreme, OpenBOR exposes low-level primitives and engine objects through configuration and script. Creators can inspect runtime state, customize selected behavior, replace complete subsystems, or build new ones without modifying engine source or depending on third-party plugins. Native functionality is therefore a starting point, not a boundary.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and over [[:Category:Script Events|70 event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance -&#039;&#039;&#039; Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Model_Lifecycle_Scripts&amp;diff=1074</id>
		<title>Model Lifecycle Scripts</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Model_Lifecycle_Scripts&amp;diff=1074"/>
		<updated>2026-08-22T03:49:31Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Model]]&lt;br /&gt;
[[Category:Script]]&lt;br /&gt;
&#039;&#039;&#039;Model lifecycle scripts&#039;&#039;&#039; are event hooks that execute when a model template enters or leaves OpenBOR&#039;s loaded model collection. The series provides project-wide global observers and model-owned callbacks for both load and unload transitions.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Scope&lt;br /&gt;
! Execution point&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Model-owned&lt;br /&gt;
| After that model finishes loading&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After the loaded model&#039;s &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Model-owned&lt;br /&gt;
| Before that model begins native destruction&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After the model&#039;s &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt;, before native destruction&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Global Usage ==&lt;br /&gt;
&lt;br /&gt;
Create either or both global files in &amp;lt;code&amp;gt;data/scripts&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads them automatically. &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; observes every completed model load, while &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; observes every model immediately before it is destroyed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;last_loaded_model_name&amp;quot;, modelname);&lt;br /&gt;
    setglobalvar(&amp;quot;last_loaded_model_index&amp;quot;, modelindex);&lt;br /&gt;
    setglobalvar(&amp;quot;model_registry_dirty&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example is suitable for &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt;. 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|loading.c]] execution.&lt;br /&gt;
&lt;br /&gt;
== Model Usage ==&lt;br /&gt;
&lt;br /&gt;
Add either command to a model definition and provide the path to its script file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
modelloadscript data/scripts/models/boss_template_load.c&lt;br /&gt;
modelunloadscript data/scripts/models/boss_template_unload.c&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The commands belong to the model template that declares them. They do not belong to spawned entities and are not copied into entity &amp;lt;code&amp;gt;modeldata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;boss_template_ready&amp;quot;, 1);&lt;br /&gt;
    setglobalvar(&amp;quot;boss_template_index&amp;quot;, modelindex);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model-owned callbacks are useful when one template requires specialized setup or teardown that does not belong in the global observer.&lt;br /&gt;
&lt;br /&gt;
Only one &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and one &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; may be assigned to a model. Global and model-owned layers may be used independently or together.&lt;br /&gt;
&lt;br /&gt;
== Inline Model Scripts ==&lt;br /&gt;
&lt;br /&gt;
Both commands support inline script blocks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
modelloadscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;guardian_template_loaded&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&lt;br /&gt;
modelunloadscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;guardian_template_loaded&amp;quot;, 0);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Event Data ==&lt;br /&gt;
&lt;br /&gt;
All four lifecycle hooks receive the same automatic local variables:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Variable&lt;br /&gt;
! Type&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt;&lt;br /&gt;
| Object pointer&lt;br /&gt;
| Pointer to the loaded model template. The pointer is fully available during both load and unload callbacks.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt;&lt;br /&gt;
| Integer&lt;br /&gt;
| Stable index of the model&#039;s cache entry.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt;&lt;br /&gt;
| String&lt;br /&gt;
| Name assigned to the model&#039;s cache entry.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    if (model)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;lifecycle_model_name&amp;quot;, modelname);&lt;br /&gt;
        setglobalvar(&amp;quot;lifecycle_model_index&amp;quot;, modelindex);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt; identifies the cache slot and remains stable when an unloadable model is later loaded again. &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt; is safe to preserve after an unload callback. The &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; pointer is not safe to retain after unloading completes.&lt;br /&gt;
&lt;br /&gt;
== Model Load Event ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; begins.&lt;br /&gt;
&lt;br /&gt;
The load sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR locates the model&#039;s existing cache entry.&lt;br /&gt;
# The model file is read and its template is constructed.&lt;br /&gt;
# Model properties, animations, ordinary event scripts, and lifecycle commands are parsed.&lt;br /&gt;
# Temporary parser resources are released.&lt;br /&gt;
# The model&#039;s &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt; executes when present.&lt;br /&gt;
# The model enters its normal loaded state and the original load request receives the completed template.&lt;br /&gt;
&lt;br /&gt;
Model-owned logic always executes before the global observer. The global script may therefore consume registration, flags, or other state established by the model&#039;s own callback.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Model Unload Event ==&lt;br /&gt;
&lt;br /&gt;
The unload event executes while the model remains intact and immediately before OpenBOR begins native destruction.&lt;br /&gt;
&lt;br /&gt;
The unload sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR receives a valid request to unload a currently loaded model or begins a native model-cleanup operation.&lt;br /&gt;
# The model&#039;s &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt; executes when present.&lt;br /&gt;
# OpenBOR frees the template&#039;s animations, palettes, scripts, collections, and other owned resources.&lt;br /&gt;
# The template is removed from the loaded model collection while its cache entry remains available for a later reload.&lt;br /&gt;
&lt;br /&gt;
Model-owned teardown always executes before the global observer. Both layers may inspect the model and coordinate cleanup while &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; is still valid.&lt;br /&gt;
&lt;br /&gt;
The pointer becomes invalid after the unload chain returns and native destruction begins. Cleanup systems that need persistent identification should retain &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt;, or creator-owned data instead of retaining the pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;last_unloaded_model_name&amp;quot;, modelname);&lt;br /&gt;
    setglobalvar(&amp;quot;last_unloaded_model_index&amp;quot;, modelindex);&lt;br /&gt;
    setglobalvar(&amp;quot;model_registry_dirty&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example allows a registry manager to remove or rebuild the affected entry without touching the model pointer after the event ends.&lt;br /&gt;
&lt;br /&gt;
== Cache and Reload Behavior ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR distinguishes a cached model definition from a loaded model template. Entries created from &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt; retain the model name, source path, flags, and stable cache position even while the full template is unloaded.&lt;br /&gt;
&lt;br /&gt;
Lifecycle scripts follow transitions of the full template:&lt;br /&gt;
&lt;br /&gt;
* Registering the cache entry alone does not execute &amp;lt;code&amp;gt;modelload&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Loading and completing the template executes the load chain once.&lt;br /&gt;
* Looking up or requesting an already loaded template does not repeat the chain.&lt;br /&gt;
* Unloading the template executes the unload chain once.&lt;br /&gt;
* Loading the same cache entry again parses its model lifecycle commands and executes a new load chain.&lt;br /&gt;
* Requesting &amp;lt;code&amp;gt;unload_model()&amp;lt;/code&amp;gt; for an absent model does not load it and therefore produces no lifecycle event.&lt;br /&gt;
&lt;br /&gt;
This behavior provides precise change notifications without requiring a recurring scan to discover whether the loaded collection changed.&lt;br /&gt;
&lt;br /&gt;
== Lifecycle Safety ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Model subclasses do not inherit &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== Lifecycle Events and Entity Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Object represented&lt;br /&gt;
! Frequency&lt;br /&gt;
! Typical responsibility&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Loaded model template&lt;br /&gt;
| Once per actual template load&lt;br /&gt;
| Template registration, analysis, and initialization&lt;br /&gt;
|-&lt;br /&gt;
| [[Onmodelcopyscript|onmodelcopyscript]]&lt;br /&gt;
| Model data being copied for an entity&lt;br /&gt;
| During applicable model-copy operations&lt;br /&gt;
| Per-instance model-data adjustment&lt;br /&gt;
|-&lt;br /&gt;
| [[Onspawnscript|onspawnscript]]&lt;br /&gt;
| Newly created entity&lt;br /&gt;
| Once per applicable entity creation&lt;br /&gt;
| Entity initialization&lt;br /&gt;
|-&lt;br /&gt;
| [[Ondeathscript|ondeathscript]]&lt;br /&gt;
| Dying entity&lt;br /&gt;
| During applicable entity death processing&lt;br /&gt;
| Entity-specific death behavior&lt;br /&gt;
|-&lt;br /&gt;
| [[Onkillscript|onkillscript]]&lt;br /&gt;
| Entity being removed&lt;br /&gt;
| Immediately before applicable entity removal&lt;br /&gt;
| Entity cleanup&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Loaded model template&lt;br /&gt;
| Once per actual template unload&lt;br /&gt;
| Template deregistration and teardown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
One loaded model may create many entities. Model lifecycle scripts execute for the template transition, while entity events execute separately for individual instances.&lt;br /&gt;
&lt;br /&gt;
== Global and Model-Owned Responsibilities ==&lt;br /&gt;
&lt;br /&gt;
The two scopes support a useful division of responsibility:&lt;br /&gt;
&lt;br /&gt;
* Model-owned &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; handle behavior unique to one template.&lt;br /&gt;
* Global &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; maintain project-wide registries and systems after the model-owned callback has run.&lt;br /&gt;
&lt;br /&gt;
For example, a boss model can publish its specialized combat role during &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;. Global &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; can then add the completed template to a project-wide model registry. During removal, the model-owned callback can release its custom relationships before &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; reconciles the common registry.&lt;br /&gt;
&lt;br /&gt;
== Other Uses ==&lt;br /&gt;
&lt;br /&gt;
Quick applications include:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Model registries&#039;&#039;&#039; - Add and remove exact cache entries without repeatedly scanning every loaded model.&lt;br /&gt;
* &#039;&#039;&#039;Template analysis&#039;&#039;&#039; - Inspect capabilities, animations, classifications, resources, or creator-defined metadata once after parsing.&lt;br /&gt;
* &#039;&#039;&#039;Subsystem registration&#039;&#039;&#039; - Connect models to combat managers, factories, selectors, randomizers, encounter systems, or development tools.&lt;br /&gt;
* &#039;&#039;&#039;Dynamic loading&#039;&#039;&#039; - Track runtime &amp;lt;code&amp;gt;loadmodel()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;unload_model()&amp;lt;/code&amp;gt; transitions as they occur.&lt;br /&gt;
* &#039;&#039;&#039;Model-specific initialization&#039;&#039;&#039; - Prepare template-owned creator state before any entity needs to use it.&lt;br /&gt;
* &#039;&#039;&#039;Resource coordination&#039;&#039;&#039; - Build or release creator-managed lookup tables, auxiliary assets, caches, or relationships alongside a template.&lt;br /&gt;
* &#039;&#039;&#039;Diagnostics&#039;&#039;&#039; - Log load order, unload order, names, cache indices, reloads, or unexpected lifecycle transitions.&lt;br /&gt;
* &#039;&#039;&#039;Loading optimization&#039;&#039;&#039; - Replace repeated full-model scans in &amp;lt;code&amp;gt;loading.c&amp;lt;/code&amp;gt; with exact event-driven notifications.&lt;br /&gt;
&lt;br /&gt;
== Related Scripts ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Script&lt;br /&gt;
! Relationship&lt;br /&gt;
|-&lt;br /&gt;
| [[Loading|loading.c]]&lt;br /&gt;
| Recurring loading-screen hook. Model lifecycle events provide exact template transition notifications without repeated collection scans.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onmodelcopyscript|onmodelcopyscript]]&lt;br /&gt;
| Executes during applicable model-data copy operations rather than model-cache loading.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onspawnscript|onspawnscript]]&lt;br /&gt;
| Executes for applicable entity creation after a model template is available.&lt;br /&gt;
|-&lt;br /&gt;
| [[Ondeathscript|ondeathscript]]&lt;br /&gt;
| Model-owned hook for entity death processing.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onkillscript|onkillscript]]&lt;br /&gt;
| Model-owned hook for entity removal.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Loading]]&lt;br /&gt;
* [[Model Properties]]&lt;br /&gt;
* [[Onmodelcopyscript]]&lt;br /&gt;
* [[Onspawnscript]]&lt;br /&gt;
* [[Ondeathscript]]&lt;br /&gt;
* [[Onkillscript]]&lt;br /&gt;
* [[Models]]&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
[[Category:Openbor]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Modelunloadscript&amp;diff=1073</id>
		<title>Modelunloadscript</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Modelunloadscript&amp;diff=1073"/>
		<updated>2026-08-22T03:48:45Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Model Lifecycle Scripts#Model Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Model_Lifecycle_Scripts#Model_Usage]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Model]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Modelloadscript&amp;diff=1072</id>
		<title>Modelloadscript</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Modelloadscript&amp;diff=1072"/>
		<updated>2026-08-22T03:48:19Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Model Lifecycle Scripts#Model Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Model_Lifecycle_Scripts#Model_Usage]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Model]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Modelunload&amp;diff=1071</id>
		<title>Modelunload</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Modelunload&amp;diff=1071"/>
		<updated>2026-08-22T03:47:23Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Model Lifecycle Scripts#Global Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Model Lifecycle Scripts#Global Usage]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Model]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Modelload&amp;diff=1070</id>
		<title>Modelload</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Modelload&amp;diff=1070"/>
		<updated>2026-08-22T03:47:04Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Model Lifecycle Scripts#Global Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Model Lifecycle Scripts#Global Usage]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Model]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Model_Load_Scripts&amp;diff=1069</id>
		<title>Model Load Scripts</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Model_Load_Scripts&amp;diff=1069"/>
		<updated>2026-08-22T03:45:24Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Dcurrent moved page Model Load Scripts to Model Lifecycle Scripts: Misspelled title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Model Lifecycle Scripts]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Model_Lifecycle_Scripts&amp;diff=1068</id>
		<title>Model Lifecycle Scripts</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Model_Lifecycle_Scripts&amp;diff=1068"/>
		<updated>2026-08-22T03:45:24Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Dcurrent moved page Model Load Scripts to Model Lifecycle Scripts: Misspelled title&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Model]]&lt;br /&gt;
[[Category:Script]]&lt;br /&gt;
[[Category:Script Events]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Model lifecycle scripts&#039;&#039;&#039; are event hooks that execute when a model template enters or leaves OpenBOR&#039;s loaded model collection. The series provides project-wide global observers and model-owned callbacks for both load and unload transitions.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Scope&lt;br /&gt;
! Execution point&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Model-owned&lt;br /&gt;
| After that model finishes loading&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After the loaded model&#039;s &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Model-owned&lt;br /&gt;
| Before that model begins native destruction&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After the model&#039;s &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt;, before native destruction&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Global Usage ==&lt;br /&gt;
&lt;br /&gt;
Create either or both global files in &amp;lt;code&amp;gt;data/scripts&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads them automatically. &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; observes every completed model load, while &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; observes every model immediately before it is destroyed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;last_loaded_model_name&amp;quot;, modelname);&lt;br /&gt;
    setglobalvar(&amp;quot;last_loaded_model_index&amp;quot;, modelindex);&lt;br /&gt;
    setglobalvar(&amp;quot;model_registry_dirty&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example is suitable for &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt;. 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|loading.c]] execution.&lt;br /&gt;
&lt;br /&gt;
== Model Usage ==&lt;br /&gt;
&lt;br /&gt;
Add either command to a model definition and provide the path to its script file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
modelloadscript data/scripts/models/boss_template_load.c&lt;br /&gt;
modelunloadscript data/scripts/models/boss_template_unload.c&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The commands belong to the model template that declares them. They do not belong to spawned entities and are not copied into entity &amp;lt;code&amp;gt;modeldata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;boss_template_ready&amp;quot;, 1);&lt;br /&gt;
    setglobalvar(&amp;quot;boss_template_index&amp;quot;, modelindex);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model-owned callbacks are useful when one template requires specialized setup or teardown that does not belong in the global observer.&lt;br /&gt;
&lt;br /&gt;
Only one &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and one &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; may be assigned to a model. Global and model-owned layers may be used independently or together.&lt;br /&gt;
&lt;br /&gt;
== Inline Model Scripts ==&lt;br /&gt;
&lt;br /&gt;
Both commands support inline script blocks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
modelloadscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;guardian_template_loaded&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&lt;br /&gt;
modelunloadscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;guardian_template_loaded&amp;quot;, 0);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Event Data ==&lt;br /&gt;
&lt;br /&gt;
All four lifecycle hooks receive the same automatic local variables:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Variable&lt;br /&gt;
! Type&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt;&lt;br /&gt;
| Object pointer&lt;br /&gt;
| Pointer to the loaded model template. The pointer is fully available during both load and unload callbacks.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt;&lt;br /&gt;
| Integer&lt;br /&gt;
| Stable index of the model&#039;s cache entry.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt;&lt;br /&gt;
| String&lt;br /&gt;
| Name assigned to the model&#039;s cache entry.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    if (model)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;lifecycle_model_name&amp;quot;, modelname);&lt;br /&gt;
        setglobalvar(&amp;quot;lifecycle_model_index&amp;quot;, modelindex);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt; identifies the cache slot and remains stable when an unloadable model is later loaded again. &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt; is safe to preserve after an unload callback. The &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; pointer is not safe to retain after unloading completes.&lt;br /&gt;
&lt;br /&gt;
== Model Load Event ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; begins.&lt;br /&gt;
&lt;br /&gt;
The load sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR locates the model&#039;s existing cache entry.&lt;br /&gt;
# The model file is read and its template is constructed.&lt;br /&gt;
# Model properties, animations, ordinary event scripts, and lifecycle commands are parsed.&lt;br /&gt;
# Temporary parser resources are released.&lt;br /&gt;
# The model&#039;s &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt; executes when present.&lt;br /&gt;
# The model enters its normal loaded state and the original load request receives the completed template.&lt;br /&gt;
&lt;br /&gt;
Model-owned logic always executes before the global observer. The global script may therefore consume registration, flags, or other state established by the model&#039;s own callback.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Model Unload Event ==&lt;br /&gt;
&lt;br /&gt;
The unload event executes while the model remains intact and immediately before OpenBOR begins native destruction.&lt;br /&gt;
&lt;br /&gt;
The unload sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR receives a valid request to unload a currently loaded model or begins a native model-cleanup operation.&lt;br /&gt;
# The model&#039;s &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt; executes when present.&lt;br /&gt;
# OpenBOR frees the template&#039;s animations, palettes, scripts, collections, and other owned resources.&lt;br /&gt;
# The template is removed from the loaded model collection while its cache entry remains available for a later reload.&lt;br /&gt;
&lt;br /&gt;
Model-owned teardown always executes before the global observer. Both layers may inspect the model and coordinate cleanup while &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; is still valid.&lt;br /&gt;
&lt;br /&gt;
The pointer becomes invalid after the unload chain returns and native destruction begins. Cleanup systems that need persistent identification should retain &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt;, or creator-owned data instead of retaining the pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;last_unloaded_model_name&amp;quot;, modelname);&lt;br /&gt;
    setglobalvar(&amp;quot;last_unloaded_model_index&amp;quot;, modelindex);&lt;br /&gt;
    setglobalvar(&amp;quot;model_registry_dirty&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example allows a registry manager to remove or rebuild the affected entry without touching the model pointer after the event ends.&lt;br /&gt;
&lt;br /&gt;
== Cache and Reload Behavior ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR distinguishes a cached model definition from a loaded model template. Entries created from &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt; retain the model name, source path, flags, and stable cache position even while the full template is unloaded.&lt;br /&gt;
&lt;br /&gt;
Lifecycle scripts follow transitions of the full template:&lt;br /&gt;
&lt;br /&gt;
* Registering the cache entry alone does not execute &amp;lt;code&amp;gt;modelload&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Loading and completing the template executes the load chain once.&lt;br /&gt;
* Looking up or requesting an already loaded template does not repeat the chain.&lt;br /&gt;
* Unloading the template executes the unload chain once.&lt;br /&gt;
* Loading the same cache entry again parses its model lifecycle commands and executes a new load chain.&lt;br /&gt;
* Requesting &amp;lt;code&amp;gt;unload_model()&amp;lt;/code&amp;gt; for an absent model does not load it and therefore produces no lifecycle event.&lt;br /&gt;
&lt;br /&gt;
This behavior provides precise change notifications without requiring a recurring scan to discover whether the loaded collection changed.&lt;br /&gt;
&lt;br /&gt;
== Lifecycle Safety ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Model subclasses do not inherit &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== Lifecycle Events and Entity Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Object represented&lt;br /&gt;
! Frequency&lt;br /&gt;
! Typical responsibility&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Loaded model template&lt;br /&gt;
| Once per actual template load&lt;br /&gt;
| Template registration, analysis, and initialization&lt;br /&gt;
|-&lt;br /&gt;
| [[Onmodelcopyscript|onmodelcopyscript]]&lt;br /&gt;
| Model data being copied for an entity&lt;br /&gt;
| During applicable model-copy operations&lt;br /&gt;
| Per-instance model-data adjustment&lt;br /&gt;
|-&lt;br /&gt;
| [[Onspawnscript|onspawnscript]]&lt;br /&gt;
| Newly created entity&lt;br /&gt;
| Once per applicable entity creation&lt;br /&gt;
| Entity initialization&lt;br /&gt;
|-&lt;br /&gt;
| [[Ondeathscript|ondeathscript]]&lt;br /&gt;
| Dying entity&lt;br /&gt;
| During applicable entity death processing&lt;br /&gt;
| Entity-specific death behavior&lt;br /&gt;
|-&lt;br /&gt;
| [[Onkillscript|onkillscript]]&lt;br /&gt;
| Entity being removed&lt;br /&gt;
| Immediately before applicable entity removal&lt;br /&gt;
| Entity cleanup&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Loaded model template&lt;br /&gt;
| Once per actual template unload&lt;br /&gt;
| Template deregistration and teardown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
One loaded model may create many entities. Model lifecycle scripts execute for the template transition, while entity events execute separately for individual instances.&lt;br /&gt;
&lt;br /&gt;
== Global and Model-Owned Responsibilities ==&lt;br /&gt;
&lt;br /&gt;
The two scopes support a useful division of responsibility:&lt;br /&gt;
&lt;br /&gt;
* Model-owned &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; handle behavior unique to one template.&lt;br /&gt;
* Global &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; maintain project-wide registries and systems after the model-owned callback has run.&lt;br /&gt;
&lt;br /&gt;
For example, a boss model can publish its specialized combat role during &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;. Global &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; can then add the completed template to a project-wide model registry. During removal, the model-owned callback can release its custom relationships before &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; reconciles the common registry.&lt;br /&gt;
&lt;br /&gt;
== Other Uses ==&lt;br /&gt;
&lt;br /&gt;
Quick applications include:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Model registries&#039;&#039;&#039; - Add and remove exact cache entries without repeatedly scanning every loaded model.&lt;br /&gt;
* &#039;&#039;&#039;Template analysis&#039;&#039;&#039; - Inspect capabilities, animations, classifications, resources, or creator-defined metadata once after parsing.&lt;br /&gt;
* &#039;&#039;&#039;Subsystem registration&#039;&#039;&#039; - Connect models to combat managers, factories, selectors, randomizers, encounter systems, or development tools.&lt;br /&gt;
* &#039;&#039;&#039;Dynamic loading&#039;&#039;&#039; - Track runtime &amp;lt;code&amp;gt;loadmodel()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;unload_model()&amp;lt;/code&amp;gt; transitions as they occur.&lt;br /&gt;
* &#039;&#039;&#039;Model-specific initialization&#039;&#039;&#039; - Prepare template-owned creator state before any entity needs to use it.&lt;br /&gt;
* &#039;&#039;&#039;Resource coordination&#039;&#039;&#039; - Build or release creator-managed lookup tables, auxiliary assets, caches, or relationships alongside a template.&lt;br /&gt;
* &#039;&#039;&#039;Diagnostics&#039;&#039;&#039; - Log load order, unload order, names, cache indices, reloads, or unexpected lifecycle transitions.&lt;br /&gt;
* &#039;&#039;&#039;Loading optimization&#039;&#039;&#039; - Replace repeated full-model scans in &amp;lt;code&amp;gt;loading.c&amp;lt;/code&amp;gt; with exact event-driven notifications.&lt;br /&gt;
&lt;br /&gt;
== Related Scripts ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Script&lt;br /&gt;
! Relationship&lt;br /&gt;
|-&lt;br /&gt;
| [[Loading|loading.c]]&lt;br /&gt;
| Recurring loading-screen hook. Model lifecycle events provide exact template transition notifications without repeated collection scans.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onmodelcopyscript|onmodelcopyscript]]&lt;br /&gt;
| Executes during applicable model-data copy operations rather than model-cache loading.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onspawnscript|onspawnscript]]&lt;br /&gt;
| Executes for applicable entity creation after a model template is available.&lt;br /&gt;
|-&lt;br /&gt;
| [[Ondeathscript|ondeathscript]]&lt;br /&gt;
| Model-owned hook for entity death processing.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onkillscript|onkillscript]]&lt;br /&gt;
| Model-owned hook for entity removal.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Loading]]&lt;br /&gt;
* [[Model Properties]]&lt;br /&gt;
* [[Onmodelcopyscript]]&lt;br /&gt;
* [[Onspawnscript]]&lt;br /&gt;
* [[Ondeathscript]]&lt;br /&gt;
* [[Onkillscript]]&lt;br /&gt;
* [[Models]]&lt;br /&gt;
* [[Script Overview]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Model_Lifecycle_Scripts&amp;diff=1067</id>
		<title>Model Lifecycle Scripts</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Model_Lifecycle_Scripts&amp;diff=1067"/>
		<updated>2026-08-22T03:44:33Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Created page with &amp;quot;Category:Model Category:Script Category:Script Events  &amp;#039;&amp;#039;&amp;#039;Model lifecycle scripts&amp;#039;&amp;#039;&amp;#039; are event hooks that execute when a model template enters or leaves OpenBOR&amp;#039;s loaded model collection. The series provides project-wide global observers and model-owned callbacks for both load and unload transitions.  {| class=&amp;quot;wikitable&amp;quot; ! Hook ! Scope ! Execution point |- | &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; | Model-owned | After that model finishes loading |- | &amp;lt;code&amp;gt;data/script...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Model]]&lt;br /&gt;
[[Category:Script]]&lt;br /&gt;
[[Category:Script Events]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Model lifecycle scripts&#039;&#039;&#039; are event hooks that execute when a model template enters or leaves OpenBOR&#039;s loaded model collection. The series provides project-wide global observers and model-owned callbacks for both load and unload transitions.&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Scope&lt;br /&gt;
! Execution point&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Model-owned&lt;br /&gt;
| After that model finishes loading&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After the loaded model&#039;s &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Model-owned&lt;br /&gt;
| Before that model begins native destruction&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After the model&#039;s &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt;, before native destruction&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Global Usage ==&lt;br /&gt;
&lt;br /&gt;
Create either or both global files in &amp;lt;code&amp;gt;data/scripts&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads them automatically. &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; observes every completed model load, while &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; observes every model immediately before it is destroyed.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;last_loaded_model_name&amp;quot;, modelname);&lt;br /&gt;
    setglobalvar(&amp;quot;last_loaded_model_index&amp;quot;, modelindex);&lt;br /&gt;
    setglobalvar(&amp;quot;model_registry_dirty&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example is suitable for &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt;. 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|loading.c]] execution.&lt;br /&gt;
&lt;br /&gt;
== Model Usage ==&lt;br /&gt;
&lt;br /&gt;
Add either command to a model definition and provide the path to its script file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
modelloadscript data/scripts/models/boss_template_load.c&lt;br /&gt;
modelunloadscript data/scripts/models/boss_template_unload.c&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The commands belong to the model template that declares them. They do not belong to spawned entities and are not copied into entity &amp;lt;code&amp;gt;modeldata&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;boss_template_ready&amp;quot;, 1);&lt;br /&gt;
    setglobalvar(&amp;quot;boss_template_index&amp;quot;, modelindex);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Model-owned callbacks are useful when one template requires specialized setup or teardown that does not belong in the global observer.&lt;br /&gt;
&lt;br /&gt;
Only one &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and one &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; may be assigned to a model. Global and model-owned layers may be used independently or together.&lt;br /&gt;
&lt;br /&gt;
== Inline Model Scripts ==&lt;br /&gt;
&lt;br /&gt;
Both commands support inline script blocks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
modelloadscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;guardian_template_loaded&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&lt;br /&gt;
modelunloadscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;guardian_template_loaded&amp;quot;, 0);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Event Data ==&lt;br /&gt;
&lt;br /&gt;
All four lifecycle hooks receive the same automatic local variables:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable sortable&amp;quot;&lt;br /&gt;
! Variable&lt;br /&gt;
! Type&lt;br /&gt;
! Description&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt;&lt;br /&gt;
| Object pointer&lt;br /&gt;
| Pointer to the loaded model template. The pointer is fully available during both load and unload callbacks.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt;&lt;br /&gt;
| Integer&lt;br /&gt;
| Stable index of the model&#039;s cache entry.&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt;&lt;br /&gt;
| String&lt;br /&gt;
| Name assigned to the model&#039;s cache entry.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    if (model)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;lifecycle_model_name&amp;quot;, modelname);&lt;br /&gt;
        setglobalvar(&amp;quot;lifecycle_model_index&amp;quot;, modelindex);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt; identifies the cache slot and remains stable when an unloadable model is later loaded again. &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt; is safe to preserve after an unload callback. The &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; pointer is not safe to retain after unloading completes.&lt;br /&gt;
&lt;br /&gt;
== Model Load Event ==&lt;br /&gt;
&lt;br /&gt;
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 &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; begins.&lt;br /&gt;
&lt;br /&gt;
The load sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR locates the model&#039;s existing cache entry.&lt;br /&gt;
# The model file is read and its template is constructed.&lt;br /&gt;
# Model properties, animations, ordinary event scripts, and lifecycle commands are parsed.&lt;br /&gt;
# Temporary parser resources are released.&lt;br /&gt;
# The model&#039;s &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/modelload.c&amp;lt;/code&amp;gt; executes when present.&lt;br /&gt;
# The model enters its normal loaded state and the original load request receives the completed template.&lt;br /&gt;
&lt;br /&gt;
Model-owned logic always executes before the global observer. The global script may therefore consume registration, flags, or other state established by the model&#039;s own callback.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
== Model Unload Event ==&lt;br /&gt;
&lt;br /&gt;
The unload event executes while the model remains intact and immediately before OpenBOR begins native destruction.&lt;br /&gt;
&lt;br /&gt;
The unload sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR receives a valid request to unload a currently loaded model or begins a native model-cleanup operation.&lt;br /&gt;
# The model&#039;s &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/modelunload.c&amp;lt;/code&amp;gt; executes when present.&lt;br /&gt;
# OpenBOR frees the template&#039;s animations, palettes, scripts, collections, and other owned resources.&lt;br /&gt;
# The template is removed from the loaded model collection while its cache entry remains available for a later reload.&lt;br /&gt;
&lt;br /&gt;
Model-owned teardown always executes before the global observer. Both layers may inspect the model and coordinate cleanup while &amp;lt;code&amp;gt;model&amp;lt;/code&amp;gt; is still valid.&lt;br /&gt;
&lt;br /&gt;
The pointer becomes invalid after the unload chain returns and native destruction begins. Cleanup systems that need persistent identification should retain &amp;lt;code&amp;gt;modelname&amp;lt;/code&amp;gt;, &amp;lt;code&amp;gt;modelindex&amp;lt;/code&amp;gt;, or creator-owned data instead of retaining the pointer.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;last_unloaded_model_name&amp;quot;, modelname);&lt;br /&gt;
    setglobalvar(&amp;quot;last_unloaded_model_index&amp;quot;, modelindex);&lt;br /&gt;
    setglobalvar(&amp;quot;model_registry_dirty&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example allows a registry manager to remove or rebuild the affected entry without touching the model pointer after the event ends.&lt;br /&gt;
&lt;br /&gt;
== Cache and Reload Behavior ==&lt;br /&gt;
&lt;br /&gt;
OpenBOR distinguishes a cached model definition from a loaded model template. Entries created from &amp;lt;code&amp;gt;data/models.txt&amp;lt;/code&amp;gt; retain the model name, source path, flags, and stable cache position even while the full template is unloaded.&lt;br /&gt;
&lt;br /&gt;
Lifecycle scripts follow transitions of the full template:&lt;br /&gt;
&lt;br /&gt;
* Registering the cache entry alone does not execute &amp;lt;code&amp;gt;modelload&amp;lt;/code&amp;gt;.&lt;br /&gt;
* Loading and completing the template executes the load chain once.&lt;br /&gt;
* Looking up or requesting an already loaded template does not repeat the chain.&lt;br /&gt;
* Unloading the template executes the unload chain once.&lt;br /&gt;
* Loading the same cache entry again parses its model lifecycle commands and executes a new load chain.&lt;br /&gt;
* Requesting &amp;lt;code&amp;gt;unload_model()&amp;lt;/code&amp;gt; for an absent model does not load it and therefore produces no lifecycle event.&lt;br /&gt;
&lt;br /&gt;
This behavior provides precise change notifications without requiring a recurring scan to discover whether the loaded collection changed.&lt;br /&gt;
&lt;br /&gt;
== Lifecycle Safety ==&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
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.&lt;br /&gt;
&lt;br /&gt;
Model subclasses do not inherit &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; or &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; 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.&lt;br /&gt;
&lt;br /&gt;
== Lifecycle Events and Entity Events ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Object represented&lt;br /&gt;
! Frequency&lt;br /&gt;
! Typical responsibility&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Loaded model template&lt;br /&gt;
| Once per actual template load&lt;br /&gt;
| Template registration, analysis, and initialization&lt;br /&gt;
|-&lt;br /&gt;
| [[Onmodelcopyscript|onmodelcopyscript]]&lt;br /&gt;
| Model data being copied for an entity&lt;br /&gt;
| During applicable model-copy operations&lt;br /&gt;
| Per-instance model-data adjustment&lt;br /&gt;
|-&lt;br /&gt;
| [[Onspawnscript|onspawnscript]]&lt;br /&gt;
| Newly created entity&lt;br /&gt;
| Once per applicable entity creation&lt;br /&gt;
| Entity initialization&lt;br /&gt;
|-&lt;br /&gt;
| [[Ondeathscript|ondeathscript]]&lt;br /&gt;
| Dying entity&lt;br /&gt;
| During applicable entity death processing&lt;br /&gt;
| Entity-specific death behavior&lt;br /&gt;
|-&lt;br /&gt;
| [[Onkillscript|onkillscript]]&lt;br /&gt;
| Entity being removed&lt;br /&gt;
| Immediately before applicable entity removal&lt;br /&gt;
| Entity cleanup&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Loaded model template&lt;br /&gt;
| Once per actual template unload&lt;br /&gt;
| Template deregistration and teardown&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
One loaded model may create many entities. Model lifecycle scripts execute for the template transition, while entity events execute separately for individual instances.&lt;br /&gt;
&lt;br /&gt;
== Global and Model-Owned Responsibilities ==&lt;br /&gt;
&lt;br /&gt;
The two scopes support a useful division of responsibility:&lt;br /&gt;
&lt;br /&gt;
* Model-owned &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunloadscript&amp;lt;/code&amp;gt; handle behavior unique to one template.&lt;br /&gt;
* Global &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; maintain project-wide registries and systems after the model-owned callback has run.&lt;br /&gt;
&lt;br /&gt;
For example, a boss model can publish its specialized combat role during &amp;lt;code&amp;gt;modelloadscript&amp;lt;/code&amp;gt;. Global &amp;lt;code&amp;gt;modelload.c&amp;lt;/code&amp;gt; can then add the completed template to a project-wide model registry. During removal, the model-owned callback can release its custom relationships before &amp;lt;code&amp;gt;modelunload.c&amp;lt;/code&amp;gt; reconciles the common registry.&lt;br /&gt;
&lt;br /&gt;
== Other Uses ==&lt;br /&gt;
&lt;br /&gt;
Quick applications include:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Model registries&#039;&#039;&#039; - Add and remove exact cache entries without repeatedly scanning every loaded model.&lt;br /&gt;
* &#039;&#039;&#039;Template analysis&#039;&#039;&#039; - Inspect capabilities, animations, classifications, resources, or creator-defined metadata once after parsing.&lt;br /&gt;
* &#039;&#039;&#039;Subsystem registration&#039;&#039;&#039; - Connect models to combat managers, factories, selectors, randomizers, encounter systems, or development tools.&lt;br /&gt;
* &#039;&#039;&#039;Dynamic loading&#039;&#039;&#039; - Track runtime &amp;lt;code&amp;gt;loadmodel()&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;unload_model()&amp;lt;/code&amp;gt; transitions as they occur.&lt;br /&gt;
* &#039;&#039;&#039;Model-specific initialization&#039;&#039;&#039; - Prepare template-owned creator state before any entity needs to use it.&lt;br /&gt;
* &#039;&#039;&#039;Resource coordination&#039;&#039;&#039; - Build or release creator-managed lookup tables, auxiliary assets, caches, or relationships alongside a template.&lt;br /&gt;
* &#039;&#039;&#039;Diagnostics&#039;&#039;&#039; - Log load order, unload order, names, cache indices, reloads, or unexpected lifecycle transitions.&lt;br /&gt;
* &#039;&#039;&#039;Loading optimization&#039;&#039;&#039; - Replace repeated full-model scans in &amp;lt;code&amp;gt;loading.c&amp;lt;/code&amp;gt; with exact event-driven notifications.&lt;br /&gt;
&lt;br /&gt;
== Related Scripts ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Script&lt;br /&gt;
! Relationship&lt;br /&gt;
|-&lt;br /&gt;
| [[Loading|loading.c]]&lt;br /&gt;
| Recurring loading-screen hook. Model lifecycle events provide exact template transition notifications without repeated collection scans.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onmodelcopyscript|onmodelcopyscript]]&lt;br /&gt;
| Executes during applicable model-data copy operations rather than model-cache loading.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onspawnscript|onspawnscript]]&lt;br /&gt;
| Executes for applicable entity creation after a model template is available.&lt;br /&gt;
|-&lt;br /&gt;
| [[Ondeathscript|ondeathscript]]&lt;br /&gt;
| Model-owned hook for entity death processing.&lt;br /&gt;
|-&lt;br /&gt;
| [[Onkillscript|onkillscript]]&lt;br /&gt;
| Model-owned hook for entity removal.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Loading]]&lt;br /&gt;
* [[Model Properties]]&lt;br /&gt;
* [[Onmodelcopyscript]]&lt;br /&gt;
* [[Onspawnscript]]&lt;br /&gt;
* [[Ondeathscript]]&lt;br /&gt;
* [[Onkillscript]]&lt;br /&gt;
* [[Models]]&lt;br /&gt;
* [[Script Overview]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Logic_Update_Scripts&amp;diff=1066</id>
		<title>Logic Update Scripts</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Logic_Update_Scripts&amp;diff=1066"/>
		<updated>2026-08-22T03:42:48Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Level]]&lt;br /&gt;
[[Category:Script]]&lt;br /&gt;
&#039;&#039;&#039;Logical update scripts&#039;&#039;&#039; are recurring event hooks tied directly to [[Time#Elapsed Time|OpenBOR&#039;s logical clock]]. They provide paired entry points immediately before and after each logical tick processed by the engine.&lt;br /&gt;
&lt;br /&gt;
OpenBOR supplies both project-wide global files and level-specific commands:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Scope&lt;br /&gt;
! Execution point&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| Before native processing for each logical tick&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Current level&lt;br /&gt;
| After global &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt;, before native tick processing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After native processing for each logical tick&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Current level&lt;br /&gt;
| After global &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt;, before the logical clock advances&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These hooks differ from the outer-cycle [[Update]] and [[Updated]] events. Outer update frequency may follow display synchronization, platform timing, and runtime workload. Logical update scripts instead execute once for every simulation tick OpenBOR actually processes, including each tick of a multi-tick catch-up cycle.&lt;br /&gt;
&lt;br /&gt;
This direct relationship to the logical clock makes the series useful for deterministic state machines, tick-accurate controllers, custom simulation systems, pre-simulation preparation, post-simulation reconciliation, replay instrumentation, synchronized hazards, and other features that should advance with engine logic rather than display frequency.&lt;br /&gt;
&lt;br /&gt;
== Global Usage ==&lt;br /&gt;
&lt;br /&gt;
Create either or both global files in &amp;lt;code&amp;gt;data/scripts&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads them automatically. Both use a &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; entry point and receive no automatic event variables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    long logical_tick = openborvariant(&amp;quot;elapsed_time&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    setglobalvar(&amp;quot;logical_tick_begin&amp;quot;, logical_tick);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; is the first script in the logical-tick sequence. &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; is the first script after native tick processing.&lt;br /&gt;
&lt;br /&gt;
Projects may use either hook independently or use both as a matched pair.&lt;br /&gt;
&lt;br /&gt;
== Level Usage ==&lt;br /&gt;
&lt;br /&gt;
Add either command to a level definition and provide the path to its script file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
updatelogicscript data/scripts/levels/foundry_updatelogic.c&lt;br /&gt;
updatedlogicscript data/scripts/levels/foundry_updatedlogic.c&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each referenced script uses a &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; entry point and receives no automatic event variables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    // Perform level-specific logical-tick work here.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads these scripts with the level and executes them only while that level remains available. One &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt; and one &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; may be assigned to a level.&lt;br /&gt;
&lt;br /&gt;
== Inline Level Scripts ==&lt;br /&gt;
&lt;br /&gt;
Both level commands support inline script blocks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
updatelogicscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;foundry_tick_active&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&lt;br /&gt;
updatedlogicscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;foundry_tick_complete&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inline form is convenient for concise level-only behavior. External files are easier to reuse and organize when logical controllers contain several systems or helper functions.&lt;br /&gt;
&lt;br /&gt;
== Execution Order ==&lt;br /&gt;
&lt;br /&gt;
The relevant recurring sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR begins an outer update cycle and refreshes timing and input state.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/update.c&amp;lt;/code&amp;gt; executes when eligible.&lt;br /&gt;
# The current level&#039;s &amp;lt;code&amp;gt;updatescript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# OpenBOR processes applicable key events and determines how many logical ticks are due.&lt;br /&gt;
# For each logical tick:&lt;br /&gt;
## Global &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## The current level&#039;s &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## OpenBOR processes native logical work, including applicable scrolling, timer, status, and entity updates.&lt;br /&gt;
## Global &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## The current level&#039;s &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## OpenBOR advances the logical clock to the next tick.&lt;br /&gt;
# After all due ticks finish, OpenBOR prepares the display queue.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/updated.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
# The current level&#039;s &amp;lt;code&amp;gt;updatedscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
&lt;br /&gt;
Global logic always executes before the matching level logic. Level scripts can therefore specialize or consume project-wide state established by their global counterparts during the same phase.&lt;br /&gt;
&lt;br /&gt;
When an outer update cycle processes several logical ticks, the complete pre-tick and post-tick chain repeats for each one. When no logical tick is due, none of the four logical hooks executes during that outer cycle.&lt;br /&gt;
&lt;br /&gt;
== Logical Clock Value ==&lt;br /&gt;
&lt;br /&gt;
Both sides of the pair observe the same logical clock value. OpenBOR executes &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; after native tick processing but before incrementing the clock.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    long logical_tick = openborvariant(&amp;quot;elapsed_time&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    setglobalvar(&amp;quot;logical_tick_finished&amp;quot;, logical_tick);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This timing allows the early hook to capture or prepare state for tick &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt;, while the late hook examines the results of that same tick &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt;. The following logical iteration then begins with tick &amp;lt;code&amp;gt;N + 1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
OpenBOR uses a default logical rate of 200 ticks per second. Projects may configure a different logical clock rate, so scripts should use the clock value or project timing configuration when converting ticks into real-time units.&lt;br /&gt;
&lt;br /&gt;
== Pause and Eligibility ==&lt;br /&gt;
&lt;br /&gt;
Logical update scripts execute only while OpenBOR is actively processing logical ticks. Pausing prevents the logical loop from advancing, so neither the early nor late logical hook executes during paused outer cycles.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;alwaysupdate&amp;lt;/code&amp;gt; setting expands eligibility beyond normal in-game update calls. It does not bypass pause or create a logical tick when the current outer cycle has none to process.&lt;br /&gt;
&lt;br /&gt;
This differs from outer-cycle &amp;lt;code&amp;gt;updated&amp;lt;/code&amp;gt; hooks, which may still execute during paused in-game cycles. Systems that must remain active while paused belong in an appropriate outer update or presentation hook rather than the logical series.&lt;br /&gt;
&lt;br /&gt;
== Before and After Native Simulation ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatelogic&amp;lt;/code&amp;gt; is suited to work that must be ready before the engine updates gameplay state. Quick applications include applying queued decisions, advancing custom input state, preparing movement rules, selecting active hazards, updating custom clocks, or publishing values that entity scripts will consume during the current tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatedlogic&amp;lt;/code&amp;gt; is suited to work that depends on the results of native processing. Quick applications include resolving objectives after entity updates, reconciling custom physics, recording resulting state, counting surviving participants, evaluating synchronized conditions, or preparing state for the next logical tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    int pending_phase = getglobalvar(&amp;quot;pending_phase&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    if (pending_phase)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;active_phase&amp;quot;, pending_phase);&lt;br /&gt;
        setglobalvar(&amp;quot;pending_phase&amp;quot;, 0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example is appropriate for &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; when the new phase must be visible to every system during the upcoming native tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    int targets_remaining = getglobalvar(&amp;quot;targets_remaining&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    if (targets_remaining &amp;lt;= 0)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;pending_phase&amp;quot;, 3);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The matching &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; can evaluate values produced during native processing and queue a phase change for the next tick.&lt;br /&gt;
&lt;br /&gt;
== Choosing an Update Hook ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Frequency&lt;br /&gt;
! Phase&lt;br /&gt;
! Typical responsibility&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;update.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatescript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per eligible outer update cycle&lt;br /&gt;
| Before key events and logical processing&lt;br /&gt;
| Outer-cycle coordination and early preparation&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per processed logical tick&lt;br /&gt;
| Immediately before native tick work&lt;br /&gt;
| Tick-accurate preparation and custom simulation input&lt;br /&gt;
|-&lt;br /&gt;
| [[Updateentityscript|updateentityscript]]&lt;br /&gt;
| During applicable entity updates&lt;br /&gt;
| Within native logical processing&lt;br /&gt;
| Per-entity recurring behavior&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per processed logical tick&lt;br /&gt;
| After native tick work, before clock advancement&lt;br /&gt;
| Tick-accurate resolution and resulting-state inspection&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updated.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatedscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per eligible outer update cycle&lt;br /&gt;
| After logical processing and display preparation&lt;br /&gt;
| Late-cycle coordination and presentation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Display synchronization does not define logical-script frequency. One outer cycle may process zero, one, or several logical ticks, and the logical hooks follow the tick count exactly.&lt;br /&gt;
&lt;br /&gt;
== Other Uses ==&lt;br /&gt;
&lt;br /&gt;
Quick applications include:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Deterministic controllers&#039;&#039;&#039; - Advance custom state machines exactly once per engine logical tick.&lt;br /&gt;
* &#039;&#039;&#039;Custom simulation&#039;&#039;&#039; - Run project-defined movement, resources, hazards, clocks, or world systems beside native logic.&lt;br /&gt;
* &#039;&#039;&#039;Pre-tick preparation&#039;&#039;&#039; - Publish values, apply queued commands, choose rules, or establish state consumed during the current tick.&lt;br /&gt;
* &#039;&#039;&#039;Post-tick reconciliation&#039;&#039;&#039; - Evaluate entity results, resolve simultaneous conditions, or queue changes for the next tick.&lt;br /&gt;
* &#039;&#039;&#039;Synchronized encounters&#039;&#039;&#039; - Coordinate hazards, waves, objectives, formations, or multi-part sequences on a common clock.&lt;br /&gt;
* &#039;&#039;&#039;Replay and diagnostics&#039;&#039;&#039; - Record pre-tick and post-tick state, identify the exact logical time of a change, or compare deterministic results.&lt;br /&gt;
* &#039;&#039;&#039;Level simulation&#039;&#039;&#039; - Keep stage-specific clock logic with the level while common systems remain in the global files.&lt;br /&gt;
* &#039;&#039;&#039;Timing tools&#039;&#039;&#039; - Build profilers, counters, test harnesses, or controlled simulation instrumentation around exact tick boundaries.&lt;br /&gt;
&lt;br /&gt;
== Related Scripts ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Script&lt;br /&gt;
! Relationship&lt;br /&gt;
|-&lt;br /&gt;
| [[Update|update.c]]&lt;br /&gt;
| Global early outer-cycle hook that executes before logical processing begins.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updatescript (Level)|updatescript]]&lt;br /&gt;
| Level-specific early outer-cycle hook.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updateentityscript|updateentityscript]]&lt;br /&gt;
| Model hook executed for individual entities during native logical processing.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updated|updated.c]]&lt;br /&gt;
| Global late outer-cycle hook that executes after all due logical ticks.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updatedscript (Level)|updatedscript]]&lt;br /&gt;
| Level-specific late outer-cycle hook.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Update]]&lt;br /&gt;
* [[Updatescript (Level)]]&lt;br /&gt;
* [[Updateentityscript]]&lt;br /&gt;
* [[Updated]]&lt;br /&gt;
* [[Updatedscript (Level)]]&lt;br /&gt;
* [[Time]]&lt;br /&gt;
* [[Enhanced Delay]]&lt;br /&gt;
* [[Script Overview]]&lt;br /&gt;
[[Category:Openbor]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Updatedlogicscript&amp;diff=1065</id>
		<title>Updatedlogicscript</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Updatedlogicscript&amp;diff=1065"/>
		<updated>2026-08-22T03:42:05Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Logic Update Scripts#Level Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Logic_Update_Scripts#Level_Usage]]&lt;br /&gt;
[[Category:Level]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Updatelogicscript&amp;diff=1064</id>
		<title>Updatelogicscript</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Updatelogicscript&amp;diff=1064"/>
		<updated>2026-08-22T03:41:49Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Logic Update Scripts#Level Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Logic_Update_Scripts#Level_Usage]]&lt;br /&gt;
[[Category:Level]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Updatedlogic&amp;diff=1063</id>
		<title>Updatedlogic</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Updatedlogic&amp;diff=1063"/>
		<updated>2026-08-22T03:40:54Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Logic Update Scripts#Global Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Logic Update Scripts#Global Usage]]&lt;br /&gt;
[[Category:Level]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Updatelogic&amp;diff=1062</id>
		<title>Updatelogic</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Updatelogic&amp;diff=1062"/>
		<updated>2026-08-22T03:40:37Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Redirected page to Logic Update Scripts#Global Usage&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Logic Update Scripts#Global Usage]]&lt;br /&gt;
[[Category:Level]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Logic_Update_Scripts&amp;diff=1061</id>
		<title>Logic Update Scripts</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Logic_Update_Scripts&amp;diff=1061"/>
		<updated>2026-08-22T03:39:23Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Level]]&lt;br /&gt;
[[Category:Script]]&lt;br /&gt;
[[Category:Script Events]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Logical update scripts&#039;&#039;&#039; are recurring event hooks tied directly to [[Time#Elapsed Time|OpenBOR&#039;s logical clock]]. They provide paired entry points immediately before and after each logical tick processed by the engine.&lt;br /&gt;
&lt;br /&gt;
OpenBOR supplies both project-wide global files and level-specific commands:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Scope&lt;br /&gt;
! Execution point&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| Before native processing for each logical tick&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Current level&lt;br /&gt;
| After global &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt;, before native tick processing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After native processing for each logical tick&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Current level&lt;br /&gt;
| After global &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt;, before the logical clock advances&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These hooks differ from the outer-cycle [[Update]] and [[Updated]] events. Outer update frequency may follow display synchronization, platform timing, and runtime workload. Logical update scripts instead execute once for every simulation tick OpenBOR actually processes, including each tick of a multi-tick catch-up cycle.&lt;br /&gt;
&lt;br /&gt;
This direct relationship to the logical clock makes the series useful for deterministic state machines, tick-accurate controllers, custom simulation systems, pre-simulation preparation, post-simulation reconciliation, replay instrumentation, synchronized hazards, and other features that should advance with engine logic rather than display frequency.&lt;br /&gt;
&lt;br /&gt;
== Global Usage ==&lt;br /&gt;
&lt;br /&gt;
Create either or both global files in &amp;lt;code&amp;gt;data/scripts&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads them automatically. Both use a &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; entry point and receive no automatic event variables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    long logical_tick = openborvariant(&amp;quot;elapsed_time&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    setglobalvar(&amp;quot;logical_tick_begin&amp;quot;, logical_tick);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; is the first script in the logical-tick sequence. &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; is the first script after native tick processing.&lt;br /&gt;
&lt;br /&gt;
Projects may use either hook independently or use both as a matched pair.&lt;br /&gt;
&lt;br /&gt;
== Level Usage ==&lt;br /&gt;
&lt;br /&gt;
Add either command to a level definition and provide the path to its script file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
updatelogicscript data/scripts/levels/foundry_updatelogic.c&lt;br /&gt;
updatedlogicscript data/scripts/levels/foundry_updatedlogic.c&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each referenced script uses a &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; entry point and receives no automatic event variables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    // Perform level-specific logical-tick work here.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads these scripts with the level and executes them only while that level remains available. One &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt; and one &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; may be assigned to a level.&lt;br /&gt;
&lt;br /&gt;
== Inline Level Scripts ==&lt;br /&gt;
&lt;br /&gt;
Both level commands support inline script blocks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
updatelogicscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;foundry_tick_active&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&lt;br /&gt;
updatedlogicscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;foundry_tick_complete&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inline form is convenient for concise level-only behavior. External files are easier to reuse and organize when logical controllers contain several systems or helper functions.&lt;br /&gt;
&lt;br /&gt;
== Execution Order ==&lt;br /&gt;
&lt;br /&gt;
The relevant recurring sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR begins an outer update cycle and refreshes timing and input state.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/update.c&amp;lt;/code&amp;gt; executes when eligible.&lt;br /&gt;
# The current level&#039;s &amp;lt;code&amp;gt;updatescript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# OpenBOR processes applicable key events and determines how many logical ticks are due.&lt;br /&gt;
# For each logical tick:&lt;br /&gt;
## Global &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## The current level&#039;s &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## OpenBOR processes native logical work, including applicable scrolling, timer, status, and entity updates.&lt;br /&gt;
## Global &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## The current level&#039;s &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## OpenBOR advances the logical clock to the next tick.&lt;br /&gt;
# After all due ticks finish, OpenBOR prepares the display queue.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/updated.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
# The current level&#039;s &amp;lt;code&amp;gt;updatedscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
&lt;br /&gt;
Global logic always executes before the matching level logic. Level scripts can therefore specialize or consume project-wide state established by their global counterparts during the same phase.&lt;br /&gt;
&lt;br /&gt;
When an outer update cycle processes several logical ticks, the complete pre-tick and post-tick chain repeats for each one. When no logical tick is due, none of the four logical hooks executes during that outer cycle.&lt;br /&gt;
&lt;br /&gt;
== Logical Clock Value ==&lt;br /&gt;
&lt;br /&gt;
Both sides of the pair observe the same logical clock value. OpenBOR executes &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; after native tick processing but before incrementing the clock.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    long logical_tick = openborvariant(&amp;quot;elapsed_time&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    setglobalvar(&amp;quot;logical_tick_finished&amp;quot;, logical_tick);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This timing allows the early hook to capture or prepare state for tick &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt;, while the late hook examines the results of that same tick &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt;. The following logical iteration then begins with tick &amp;lt;code&amp;gt;N + 1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
OpenBOR uses a default logical rate of 200 ticks per second. Projects may configure a different logical clock rate, so scripts should use the clock value or project timing configuration when converting ticks into real-time units.&lt;br /&gt;
&lt;br /&gt;
== Pause and Eligibility ==&lt;br /&gt;
&lt;br /&gt;
Logical update scripts execute only while OpenBOR is actively processing logical ticks. Pausing prevents the logical loop from advancing, so neither the early nor late logical hook executes during paused outer cycles.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;alwaysupdate&amp;lt;/code&amp;gt; setting expands eligibility beyond normal in-game update calls. It does not bypass pause or create a logical tick when the current outer cycle has none to process.&lt;br /&gt;
&lt;br /&gt;
This differs from outer-cycle &amp;lt;code&amp;gt;updated&amp;lt;/code&amp;gt; hooks, which may still execute during paused in-game cycles. Systems that must remain active while paused belong in an appropriate outer update or presentation hook rather than the logical series.&lt;br /&gt;
&lt;br /&gt;
== Before and After Native Simulation ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatelogic&amp;lt;/code&amp;gt; is suited to work that must be ready before the engine updates gameplay state. Quick applications include applying queued decisions, advancing custom input state, preparing movement rules, selecting active hazards, updating custom clocks, or publishing values that entity scripts will consume during the current tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatedlogic&amp;lt;/code&amp;gt; is suited to work that depends on the results of native processing. Quick applications include resolving objectives after entity updates, reconciling custom physics, recording resulting state, counting surviving participants, evaluating synchronized conditions, or preparing state for the next logical tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    int pending_phase = getglobalvar(&amp;quot;pending_phase&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    if (pending_phase)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;active_phase&amp;quot;, pending_phase);&lt;br /&gt;
        setglobalvar(&amp;quot;pending_phase&amp;quot;, 0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example is appropriate for &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; when the new phase must be visible to every system during the upcoming native tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    int targets_remaining = getglobalvar(&amp;quot;targets_remaining&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    if (targets_remaining &amp;lt;= 0)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;pending_phase&amp;quot;, 3);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The matching &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; can evaluate values produced during native processing and queue a phase change for the next tick.&lt;br /&gt;
&lt;br /&gt;
== Choosing an Update Hook ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Frequency&lt;br /&gt;
! Phase&lt;br /&gt;
! Typical responsibility&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;update.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatescript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per eligible outer update cycle&lt;br /&gt;
| Before key events and logical processing&lt;br /&gt;
| Outer-cycle coordination and early preparation&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per processed logical tick&lt;br /&gt;
| Immediately before native tick work&lt;br /&gt;
| Tick-accurate preparation and custom simulation input&lt;br /&gt;
|-&lt;br /&gt;
| [[Updateentityscript|updateentityscript]]&lt;br /&gt;
| During applicable entity updates&lt;br /&gt;
| Within native logical processing&lt;br /&gt;
| Per-entity recurring behavior&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per processed logical tick&lt;br /&gt;
| After native tick work, before clock advancement&lt;br /&gt;
| Tick-accurate resolution and resulting-state inspection&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updated.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatedscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per eligible outer update cycle&lt;br /&gt;
| After logical processing and display preparation&lt;br /&gt;
| Late-cycle coordination and presentation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Display synchronization does not define logical-script frequency. One outer cycle may process zero, one, or several logical ticks, and the logical hooks follow the tick count exactly.&lt;br /&gt;
&lt;br /&gt;
== Other Uses ==&lt;br /&gt;
&lt;br /&gt;
Quick applications include:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Deterministic controllers&#039;&#039;&#039; - Advance custom state machines exactly once per engine logical tick.&lt;br /&gt;
* &#039;&#039;&#039;Custom simulation&#039;&#039;&#039; - Run project-defined movement, resources, hazards, clocks, or world systems beside native logic.&lt;br /&gt;
* &#039;&#039;&#039;Pre-tick preparation&#039;&#039;&#039; - Publish values, apply queued commands, choose rules, or establish state consumed during the current tick.&lt;br /&gt;
* &#039;&#039;&#039;Post-tick reconciliation&#039;&#039;&#039; - Evaluate entity results, resolve simultaneous conditions, or queue changes for the next tick.&lt;br /&gt;
* &#039;&#039;&#039;Synchronized encounters&#039;&#039;&#039; - Coordinate hazards, waves, objectives, formations, or multi-part sequences on a common clock.&lt;br /&gt;
* &#039;&#039;&#039;Replay and diagnostics&#039;&#039;&#039; - Record pre-tick and post-tick state, identify the exact logical time of a change, or compare deterministic results.&lt;br /&gt;
* &#039;&#039;&#039;Level simulation&#039;&#039;&#039; - Keep stage-specific clock logic with the level while common systems remain in the global files.&lt;br /&gt;
* &#039;&#039;&#039;Timing tools&#039;&#039;&#039; - Build profilers, counters, test harnesses, or controlled simulation instrumentation around exact tick boundaries.&lt;br /&gt;
&lt;br /&gt;
== Related Scripts ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Script&lt;br /&gt;
! Relationship&lt;br /&gt;
|-&lt;br /&gt;
| [[Update|update.c]]&lt;br /&gt;
| Global early outer-cycle hook that executes before logical processing begins.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updatescript (Level)|updatescript]]&lt;br /&gt;
| Level-specific early outer-cycle hook.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updateentityscript|updateentityscript]]&lt;br /&gt;
| Model hook executed for individual entities during native logical processing.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updated|updated.c]]&lt;br /&gt;
| Global late outer-cycle hook that executes after all due logical ticks.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updatedscript (Level)|updatedscript]]&lt;br /&gt;
| Level-specific late outer-cycle hook.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Update]]&lt;br /&gt;
* [[Updatescript (Level)]]&lt;br /&gt;
* [[Updateentityscript]]&lt;br /&gt;
* [[Updated]]&lt;br /&gt;
* [[Updatedscript (Level)]]&lt;br /&gt;
* [[Time]]&lt;br /&gt;
* [[Enhanced Delay]]&lt;br /&gt;
* [[Script Overview]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Logic_Update_Scripts&amp;diff=1060</id>
		<title>Logic Update Scripts</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Logic_Update_Scripts&amp;diff=1060"/>
		<updated>2026-08-22T03:37:01Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: Created page with &amp;quot;Category:Level Category:Script Category:Script Events  &amp;#039;&amp;#039;&amp;#039;Logical update scripts&amp;#039;&amp;#039;&amp;#039; are recurring event hooks tied directly to OpenBOR&amp;#039;s logical clock. They provide paired entry points immediately before and after each logical tick processed by the engine.  OpenBOR supplies both project-wide global files and level-specific commands:  {| class=&amp;quot;wikitable&amp;quot; ! Hook ! Scope ! Execution point |- | &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt; | Project-wide | Before nati...&amp;quot;&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;[[Category:Level]]&lt;br /&gt;
[[Category:Script]]&lt;br /&gt;
[[Category:Script Events]]&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;Logical update scripts&#039;&#039;&#039; are recurring event hooks tied directly to OpenBOR&#039;s logical clock. They provide paired entry points immediately before and after each logical tick processed by the engine.&lt;br /&gt;
&lt;br /&gt;
OpenBOR supplies both project-wide global files and level-specific commands:&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Scope&lt;br /&gt;
! Execution point&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| Before native processing for each logical tick&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Current level&lt;br /&gt;
| After global &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt;, before native tick processing&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
| Project-wide&lt;br /&gt;
| After native processing for each logical tick&lt;br /&gt;
|-&lt;br /&gt;
| &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Current level&lt;br /&gt;
| After global &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt;, before the logical clock advances&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
These hooks differ from the outer-cycle [[Update]] and [[Updated]] events. Outer update frequency may follow display synchronization, platform timing, and runtime workload. Logical update scripts instead execute once for every simulation tick OpenBOR actually processes, including each tick of a multi-tick catch-up cycle.&lt;br /&gt;
&lt;br /&gt;
This direct relationship to the logical clock makes the series useful for deterministic state machines, tick-accurate controllers, custom simulation systems, pre-simulation preparation, post-simulation reconciliation, replay instrumentation, synchronized hazards, and other features that should advance with engine logic rather than display frequency.&lt;br /&gt;
&lt;br /&gt;
== Global Usage ==&lt;br /&gt;
&lt;br /&gt;
Create either or both global files in &amp;lt;code&amp;gt;data/scripts&amp;lt;/code&amp;gt;:&lt;br /&gt;
&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
* &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads them automatically. Both use a &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; entry point and receive no automatic event variables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    long logical_tick = openborvariant(&amp;quot;elapsed_time&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    setglobalvar(&amp;quot;logical_tick_begin&amp;quot;, logical_tick);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; is the first script in the logical-tick sequence. &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; is the first script after native tick processing.&lt;br /&gt;
&lt;br /&gt;
Projects may use either hook independently or use both as a matched pair.&lt;br /&gt;
&lt;br /&gt;
== Level Usage ==&lt;br /&gt;
&lt;br /&gt;
Add either command to a level definition and provide the path to its script file:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
updatelogicscript data/scripts/levels/foundry_updatelogic.c&lt;br /&gt;
updatedlogicscript data/scripts/levels/foundry_updatedlogic.c&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Each referenced script uses a &amp;lt;code&amp;gt;main()&amp;lt;/code&amp;gt; entry point and receives no automatic event variables.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    // Perform level-specific logical-tick work here.&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
OpenBOR loads these scripts with the level and executes them only while that level remains available. One &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt; and one &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; may be assigned to a level.&lt;br /&gt;
&lt;br /&gt;
== Inline Level Scripts ==&lt;br /&gt;
&lt;br /&gt;
Both level commands support inline script blocks:&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;text&amp;quot;&amp;gt;&lt;br /&gt;
updatelogicscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;foundry_tick_active&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&lt;br /&gt;
updatedlogicscript @script&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    setglobalvar(&amp;quot;foundry_tick_complete&amp;quot;, 1);&lt;br /&gt;
}&lt;br /&gt;
@end_script&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inline form is convenient for concise level-only behavior. External files are easier to reuse and organize when logical controllers contain several systems or helper functions.&lt;br /&gt;
&lt;br /&gt;
== Execution Order ==&lt;br /&gt;
&lt;br /&gt;
The relevant recurring sequence is:&lt;br /&gt;
&lt;br /&gt;
# OpenBOR begins an outer update cycle and refreshes timing and input state.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/update.c&amp;lt;/code&amp;gt; executes when eligible.&lt;br /&gt;
# The current level&#039;s &amp;lt;code&amp;gt;updatescript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
# OpenBOR processes applicable key events and determines how many logical ticks are due.&lt;br /&gt;
# For each logical tick:&lt;br /&gt;
## Global &amp;lt;code&amp;gt;data/scripts/updatelogic.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## The current level&#039;s &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## OpenBOR processes native logical work, including applicable scrolling, timer, status, and entity updates.&lt;br /&gt;
## Global &amp;lt;code&amp;gt;data/scripts/updatedlogic.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## The current level&#039;s &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
## OpenBOR advances the logical clock to the next tick.&lt;br /&gt;
# After all due ticks finish, OpenBOR prepares the display queue.&lt;br /&gt;
# Global &amp;lt;code&amp;gt;data/scripts/updated.c&amp;lt;/code&amp;gt; executes.&lt;br /&gt;
# The current level&#039;s &amp;lt;code&amp;gt;updatedscript&amp;lt;/code&amp;gt; executes when declared.&lt;br /&gt;
&lt;br /&gt;
Global logic always executes before the matching level logic. Level scripts can therefore specialize or consume project-wide state established by their global counterparts during the same phase.&lt;br /&gt;
&lt;br /&gt;
When an outer update cycle processes several logical ticks, the complete pre-tick and post-tick chain repeats for each one. When no logical tick is due, none of the four logical hooks executes during that outer cycle.&lt;br /&gt;
&lt;br /&gt;
== Logical Clock Value ==&lt;br /&gt;
&lt;br /&gt;
Both sides of the pair observe the same logical clock value. OpenBOR executes &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; and &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt; after native tick processing but before incrementing the clock.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    long logical_tick = openborvariant(&amp;quot;elapsed_time&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    setglobalvar(&amp;quot;logical_tick_finished&amp;quot;, logical_tick);&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
This timing allows the early hook to capture or prepare state for tick &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt;, while the late hook examines the results of that same tick &amp;lt;code&amp;gt;N&amp;lt;/code&amp;gt;. The following logical iteration then begins with tick &amp;lt;code&amp;gt;N + 1&amp;lt;/code&amp;gt;.&lt;br /&gt;
&lt;br /&gt;
OpenBOR uses a default logical rate of 200 ticks per second. Projects may configure a different logical clock rate, so scripts should use the clock value or project timing configuration when converting ticks into real-time units.&lt;br /&gt;
&lt;br /&gt;
== Pause and Eligibility ==&lt;br /&gt;
&lt;br /&gt;
Logical update scripts execute only while OpenBOR is actively processing logical ticks. Pausing prevents the logical loop from advancing, so neither the early nor late logical hook executes during paused outer cycles.&lt;br /&gt;
&lt;br /&gt;
The &amp;lt;code&amp;gt;alwaysupdate&amp;lt;/code&amp;gt; setting expands eligibility beyond normal in-game update calls. It does not bypass pause or create a logical tick when the current outer cycle has none to process.&lt;br /&gt;
&lt;br /&gt;
This differs from outer-cycle &amp;lt;code&amp;gt;updated&amp;lt;/code&amp;gt; hooks, which may still execute during paused in-game cycles. Systems that must remain active while paused belong in an appropriate outer update or presentation hook rather than the logical series.&lt;br /&gt;
&lt;br /&gt;
== Before and After Native Simulation ==&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatelogic&amp;lt;/code&amp;gt; is suited to work that must be ready before the engine updates gameplay state. Quick applications include applying queued decisions, advancing custom input state, preparing movement rules, selecting active hazards, updating custom clocks, or publishing values that entity scripts will consume during the current tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;code&amp;gt;updatedlogic&amp;lt;/code&amp;gt; is suited to work that depends on the results of native processing. Quick applications include resolving objectives after entity updates, reconciling custom physics, recording resulting state, counting surviving participants, evaluating synchronized conditions, or preparing state for the next logical tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    int pending_phase = getglobalvar(&amp;quot;pending_phase&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    if (pending_phase)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;active_phase&amp;quot;, pending_phase);&lt;br /&gt;
        setglobalvar(&amp;quot;pending_phase&amp;quot;, 0);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The example is appropriate for &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; when the new phase must be visible to every system during the upcoming native tick.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c&amp;quot; line&amp;gt;&lt;br /&gt;
void main()&lt;br /&gt;
{&lt;br /&gt;
    int targets_remaining = getglobalvar(&amp;quot;targets_remaining&amp;quot;);&lt;br /&gt;
&lt;br /&gt;
    if (targets_remaining &amp;lt;= 0)&lt;br /&gt;
    {&lt;br /&gt;
        setglobalvar(&amp;quot;pending_phase&amp;quot;, 3);&lt;br /&gt;
    }&lt;br /&gt;
}&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The matching &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; can evaluate values produced during native processing and queue a phase change for the next tick.&lt;br /&gt;
&lt;br /&gt;
== Choosing an Update Hook ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Hook&lt;br /&gt;
! Frequency&lt;br /&gt;
! Phase&lt;br /&gt;
! Typical responsibility&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;update.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatescript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per eligible outer update cycle&lt;br /&gt;
| Before key events and logical processing&lt;br /&gt;
| Outer-cycle coordination and early preparation&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updatelogic.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatelogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per processed logical tick&lt;br /&gt;
| Immediately before native tick work&lt;br /&gt;
| Tick-accurate preparation and custom simulation input&lt;br /&gt;
|-&lt;br /&gt;
| [[Updateentityscript|updateentityscript]]&lt;br /&gt;
| During applicable entity updates&lt;br /&gt;
| Within native logical processing&lt;br /&gt;
| Per-entity recurring behavior&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updatedlogic.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatedlogicscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per processed logical tick&lt;br /&gt;
| After native tick work, before clock advancement&lt;br /&gt;
| Tick-accurate resolution and resulting-state inspection&lt;br /&gt;
|-&lt;br /&gt;
| Global &amp;lt;code&amp;gt;updated.c&amp;lt;/code&amp;gt; and level &amp;lt;code&amp;gt;updatedscript&amp;lt;/code&amp;gt;&lt;br /&gt;
| Once per eligible outer update cycle&lt;br /&gt;
| After logical processing and display preparation&lt;br /&gt;
| Late-cycle coordination and presentation&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
Display synchronization does not define logical-script frequency. One outer cycle may process zero, one, or several logical ticks, and the logical hooks follow the tick count exactly.&lt;br /&gt;
&lt;br /&gt;
== Other Uses ==&lt;br /&gt;
&lt;br /&gt;
Quick applications include:&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Deterministic controllers&#039;&#039;&#039; - Advance custom state machines exactly once per engine logical tick.&lt;br /&gt;
* &#039;&#039;&#039;Custom simulation&#039;&#039;&#039; - Run project-defined movement, resources, hazards, clocks, or world systems beside native logic.&lt;br /&gt;
* &#039;&#039;&#039;Pre-tick preparation&#039;&#039;&#039; - Publish values, apply queued commands, choose rules, or establish state consumed during the current tick.&lt;br /&gt;
* &#039;&#039;&#039;Post-tick reconciliation&#039;&#039;&#039; - Evaluate entity results, resolve simultaneous conditions, or queue changes for the next tick.&lt;br /&gt;
* &#039;&#039;&#039;Synchronized encounters&#039;&#039;&#039; - Coordinate hazards, waves, objectives, formations, or multi-part sequences on a common clock.&lt;br /&gt;
* &#039;&#039;&#039;Replay and diagnostics&#039;&#039;&#039; - Record pre-tick and post-tick state, identify the exact logical time of a change, or compare deterministic results.&lt;br /&gt;
* &#039;&#039;&#039;Level simulation&#039;&#039;&#039; - Keep stage-specific clock logic with the level while common systems remain in the global files.&lt;br /&gt;
* &#039;&#039;&#039;Timing tools&#039;&#039;&#039; - Build profilers, counters, test harnesses, or controlled simulation instrumentation around exact tick boundaries.&lt;br /&gt;
&lt;br /&gt;
== Related Scripts ==&lt;br /&gt;
&lt;br /&gt;
{| class=&amp;quot;wikitable&amp;quot;&lt;br /&gt;
! Script&lt;br /&gt;
! Relationship&lt;br /&gt;
|-&lt;br /&gt;
| [[Update|update.c]]&lt;br /&gt;
| Global early outer-cycle hook that executes before logical processing begins.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updatescript (Level)|updatescript]]&lt;br /&gt;
| Level-specific early outer-cycle hook.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updateentityscript|updateentityscript]]&lt;br /&gt;
| Model hook executed for individual entities during native logical processing.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updated|updated.c]]&lt;br /&gt;
| Global late outer-cycle hook that executes after all due logical ticks.&lt;br /&gt;
|-&lt;br /&gt;
| [[Updatedscript (Level)|updatedscript]]&lt;br /&gt;
| Level-specific late outer-cycle hook.&lt;br /&gt;
|}&lt;br /&gt;
&lt;br /&gt;
== See Also ==&lt;br /&gt;
&lt;br /&gt;
* [[Update]]&lt;br /&gt;
* [[Updatescript (Level)]]&lt;br /&gt;
* [[Updateentityscript]]&lt;br /&gt;
* [[Updated]]&lt;br /&gt;
* [[Updatedscript (Level)]]&lt;br /&gt;
* [[Time]]&lt;br /&gt;
* [[Enhanced Delay]]&lt;br /&gt;
* [[Script Overview]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1059</id>
		<title>Main Page</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Main_Page&amp;diff=1059"/>
		<updated>2026-08-21T02:31:20Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: /* Essential Tools */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;== Introduction ==&lt;br /&gt;
&lt;br /&gt;
=== What is OpenBOR? ===&lt;br /&gt;
OpenBOR is a royalty-free, open-source engine for sprite-based games. It combines a complete native game framework - including controls, movement, combat, artificial intelligence, levels, audio, menus, saving, and local multiplayer - with an integrated C-like scripting language that can extend or replace nearly every system.&lt;br /&gt;
&lt;br /&gt;
OpenBOR specializes in side-scrolling action games such as Double Dragon and Streets of Rage, but its three-dimensional game world, configurable simulation, declarative object system, and scriptable graphics pipeline also support platformers, shooters, role-playing games, puzzle games, and other genres.&lt;br /&gt;
&lt;br /&gt;
Beginners can build complete games primarily from assets and configuration. Advanced creators can manipulate engine objects, replace native behavior, construct rendering pipelines, and even compile new script programs while a module is running.&lt;br /&gt;
&lt;br /&gt;
=== Why OpenBOR? ===&lt;br /&gt;
What sets OpenBOR apart is how far it extends in both directions. Creators do not need to build their own asset handling, controls, movement, artificial intelligence, collision and combat logic, audio playback, or other foundational systems. Everything is integrated and ready to use. Gather the assets and a fully functional game can be running within hours.&lt;br /&gt;
&lt;br /&gt;
At the opposite extreme, OpenBOR exposes low-level primitives and engine objects through configuration and script. Creators can inspect runtime state, customize selected behavior, replace complete subsystems, or build new ones without modifying engine source or depending on third-party plugins. Native functionality is therefore a starting point, not a boundary.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;Complete native gameplay&#039;&#039;&#039; - Controls, movement, gravity, level trees, platforms, combat, blocking, grabbing, throwing, artificial intelligence, cameras, audio, menus, HUDs, and saving work without custom code.&lt;br /&gt;
* &#039;&#039;&#039;[[Script Overview|Engine-wide scripting]]&#039;&#039;&#039; - Object APIs and dozens of [[:Category:Script Events|event hooks]] expose input, animation, movement, collision, damage, spawning, audio, drawing, and other runtime systems for inspection, modification, or replacement.&lt;br /&gt;
* &#039;&#039;&#039;[[Time|High-resolution simulation]]&#039;&#039;&#039; - The configurable logical clock runs at 200 Hz by default and remains independent of display updates.&lt;br /&gt;
* &#039;&#039;&#039;[[Geometry Overview|Three-dimensional sprite worlds]]&#039;&#039;&#039; - Floating-point X, Y, and Z geometry provides subpixel movement, terrain, altitude, and depth sorting.&lt;br /&gt;
* &#039;&#039;&#039;[[Collision|Declarative combat collision]]&#039;&#039;&#039; - Indexed three-dimensional attack and body boxes carry damage, reaction, motion, status, and presentation behavior.&lt;br /&gt;
* &#039;&#039;&#039;[[Factions|Composable relationships]]&#039;&#039;&#039; - Four independent 52-bit faction masks provide hardware-speed membership, hostility, direct-damage, and indirect-damage rules.&lt;br /&gt;
* &#039;&#039;&#039;[[Subscreens|Recursive graphics composition]]&#039;&#039;&#039; - Script-created subscreens support nested rendering, layer capture, transformations, masks, split screens, and feedback effects.&lt;br /&gt;
* &#039;&#039;&#039;[[Runtime Script Compilation|Runtime metaprogramming]]&#039;&#039;&#039; - Modules can load and combine source files, compile them into independently owned programs, retain persistent state, execute them repeatedly, and replace them during play.&lt;br /&gt;
* &#039;&#039;&#039;Optimized performance -&#039;&#039;&#039; Native systems, high-frequency simulation, and script integration are engineered to maintain steady frame rates and butter smooth motion with dozens or hundreds of active objects.&lt;br /&gt;
&lt;br /&gt;
== Getting Started ==&lt;br /&gt;
&lt;br /&gt;
&#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/openbor.1/ Download OpenBOR]&#039;&#039;&#039; | &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/categories/samples-and-tutorials.5/?prefix_id=3 Start Building]&#039;&#039;&#039; | &#039;&#039;&#039;[[:Category:Openbor|Browse the Reference]]&#039;&#039;&#039; | &#039;&#039;&#039;[https://chronocrash.com Visit the Community]&#039;&#039;&#039; | &#039;&#039;&#039;[https://github.com/DCurrent/openbor View the Source]&#039;&#039;&#039;&lt;br /&gt;
&lt;br /&gt;
This wiki is the comprehensive technical reference for OpenBOR, but please note it is not intended as a walk-through. For more in-depth instruction, consult the tutorials and guides available through the [https://chronocrash.com/forum ChronoCrash community].&lt;br /&gt;
&lt;br /&gt;
=== Guides and Resources ===&lt;br /&gt;
&lt;br /&gt;
* [[OpenBOR Overview]] – General overview of OpenBOR functionality and runtime layout.&lt;br /&gt;
* [[:Category:Openbor|OpenBOR Hub]] – Main jumping off point for categories and individual wiki articles.&lt;br /&gt;
* [[:Category:OpenBOR Index|OpenBOR Index]] – Simple index of OpenBOR commands, settings, and functions.&lt;br /&gt;
* [[Legacy Manual]] – Archival monolithic manual.&lt;br /&gt;
* [https://chronocrash.com/ ChronoCrash] – OpenBOR’s home community, including its help forum, downloadable games, and in-depth tutorials.&lt;br /&gt;
* [https://github.com/DCurrent/openbor OpenBOR Repository] – Public-facing OpenBOR code base.&lt;br /&gt;
&lt;br /&gt;
== Development Environment ==&lt;br /&gt;
&lt;br /&gt;
=== Essential Tools ===&lt;br /&gt;
&lt;br /&gt;
The following tools are needed to begin building games with OpenBOR. You will need a [https://chronocrash.com/forum ChronoCrash community] account to download some resources.&lt;br /&gt;
&lt;br /&gt;
* [https://www.chronocrash.com/forum/resources/categories/engine.3/ OpenBOR engine executable] – The latest stable engine release.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/neo-edit-pack.2/ Edit Pack]&#039;&#039;&#039; – Tools for packing and unpacking OpenBOR game modules.&lt;br /&gt;
* &#039;&#039;&#039;Development environment&#039;&#039;&#039; – OpenBOR uses a combination of text files and optional script files to configure game functionality. Any text editor may be used. [https://code.visualstudio.com/ Visual Studio Code] is recommended, together with [https://github.com Git] or another version-control system for managing project files.&lt;br /&gt;
* &#039;&#039;&#039;Graphics software&#039;&#039;&#039; – Any graphics-editing application capable of exporting &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; images. [https://www.adobe.com Adobe Photoshop] or [https://www.gimp.org/ GIMP] are recommended.&lt;br /&gt;
* &#039;&#039;&#039;Audio software&#039;&#039;&#039; – An audio-editing application capable of exporting &amp;lt;code&amp;gt;.wav&amp;lt;/code&amp;gt; files for sound effects and, optionally, &amp;lt;code&amp;gt;.ogg&amp;lt;/code&amp;gt; files for music. [https://www.adobe.com/products/audition.html Adobe Audition] or [https://www.audacityteam.org/ Audacity] are recommended.&lt;br /&gt;
&lt;br /&gt;
=== Optional Tools ===&lt;br /&gt;
&lt;br /&gt;
These tools are optional but highly recommended.&lt;br /&gt;
&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/chronocrash-modders-tools.139/ ChronoCrash Modders Tools (CMT)]&#039;&#039;&#039; – Visual editing application for building OpenBOR games.&lt;br /&gt;
* &#039;&#039;&#039;[https://www.chronocrash.com/forum/resources/palapply.15/ PalApply]&#039;&#039;&#039; – Applies a palette to one or more images and exports optimized &amp;lt;code&amp;gt;.png&amp;lt;/code&amp;gt; files ready for use with OpenBOR.&lt;br /&gt;
&lt;br /&gt;
== What Open Source Means ==&lt;br /&gt;
Because the term has caused confusion and disputes in the past, it is important to clarify what open source means in the context of OpenBOR.&lt;br /&gt;
&lt;br /&gt;
OpenBOR’s existing codebase is available under a permissive [https://github.com/DCurrent/openbor/blob/master/LICENSE BSD-derived license]. &#039;&#039;&#039;Creators may use the engine to make, license, and sell games and other content they rightfully own.&#039;&#039;&#039; The OpenBOR license does not claim or control that content.&lt;br /&gt;
&lt;br /&gt;
Open source does not mean a free-for-all. It provides no implied permission to modify or redistribute another creator’s work without consent. The license also permits binary distribution without requiring source disclosure - it does not compel the team to release existing or future source code, and by extension it does not entitle anyone to the source code of closed-source extensions or obligate the development team to release it. The public repository is provided and maintained as a voluntary act of good faith, not as an obligation imposed by the license.&lt;br /&gt;
&lt;br /&gt;
The OpenBOR development team may produce proprietary features, such as encrypted pack handling, and license them separately at its discretion.&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Respawn4&amp;diff=1058</id>
		<title>Respawn4</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Respawn4&amp;diff=1058"/>
		<updated>2026-08-21T01:56:41Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Player Respawn Scripts]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Player]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Respawn3&amp;diff=1057</id>
		<title>Respawn3</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Respawn3&amp;diff=1057"/>
		<updated>2026-08-21T01:56:26Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Player Respawn Scripts]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Player]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
	<entry>
		<id>https://chronocrash.com/openbor/wiki/index.php?title=Respawn2&amp;diff=1056</id>
		<title>Respawn2</title>
		<link rel="alternate" type="text/html" href="https://chronocrash.com/openbor/wiki/index.php?title=Respawn2&amp;diff=1056"/>
		<updated>2026-08-21T01:55:55Z</updated>

		<summary type="html">&lt;p&gt;Dcurrent: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Player Respawn Scripts]]&lt;br /&gt;
[[Category:OpenBOR Index]]&lt;br /&gt;
[[Category:Player]]&lt;br /&gt;
[[Category:Script Events]]&lt;/div&gt;</summary>
		<author><name>Dcurrent</name></author>
	</entry>
</feed>