Ondoattackscript
ondoattackscript is OpenBOR's programmable pre-resolution combat hook. It runs when an attack becomes a candidate to hit another entity, after collision and preliminary eligibility checks but before the engine commits to blocking, countering, damage, or reactions. OpenBOR invokes the recipient's script first and the conferring entity's script second.
Its placement gives creators control at the point where an impact exists, both participants are known, and the result is still open. Scripts may inspect the entities, attack properties, metadata, input state, and impact position; modify the shared attack context; perform custom responses; allow native resolution to continue; or replace it entirely by cancelling the candidate. This supports complete combat mechanics rather than isolated damage exceptions.
Use Didhitscript when logic should run only after OpenBOR accepts a hit.
Syntax
ondoattackscript {path}
# Default
# No Ondoattack script
{path}- Path to an OpenBOR Script source file.- The command belongs in a model definition.
- OpenBOR loads and compiles the script with the model.
- The script's return value is ignored.
Example model definition:
name example_character
type enemy
ondoattackscript data/scripts/example_ondoattack.c
Event code may also be embedded directly in the model:
ondoattackscript @script
void main()
{
void self = getlocalvar("self");
void other = getlocalvar("other");
int which = getlocalvar("which");
int attack_type = getlocalvar("attacktype");
}
@end_script
Event Scope
OpenBOR invokes ondoattackscript for candidate impacts that reach the attack-resolution stage. Relevant native paths include ordinary attack-box collisions, active item attack-box collisions, and configured damage on landing.
Preliminary rules are evaluated before the event. These include collision, target life state, invincibility, faction permission, next-hit timing, attack-ID duplication, ground-hit permission, juggle availability, and the presence of a take-damage handler. Rejected candidates do not invoke the event.
Passing the preliminary rules does not guarantee a hit. Native processing may still reject the candidate after both scripts run, or may resolve it as a block or counter. Consequently, this event can run without a later Didhit event.
Ordinary item pickup paths may invoke an item's collection script directly. ondoattackscript should not be treated as a universal item-collection callback.
self is always the entity whose model owns the currently executing script. For a projectile impact, the projectile is the conferring entity; OpenBOR does not automatically substitute its owner.
Practical Combat Patterns
The event's most useful quality is its timing. Collision has already identified a meaningful interaction, while the outcome remains under script control. Common applications include the following patterns.
| Pattern | Use of ondoattackscript
|
|---|---|
| Timing-based parry or just defense | Check whether the recipient opened a brief defense window immediately before impact. Cancel the hit, play the parry response, spawn a flash, and optionally stagger or repel the conferrer. |
| Projectile reflection or absorption | Recognize a projectile through its entity properties, owner, attack type, or tag. Cancel the original impact, then redirect, replace, absorb, or convert the projectile. |
| Directional armor and weak points | Compare participant facing, positions, and impact coordinates. Ignore attacks against protected regions, or replace them with armor sparks, guard loss, rear-hit reactions, or exposed-point damage. |
| Shields and boss phase gates | Cancel ordinary damage while a shield or phase condition is active, consume the appropriate resource, provide immediate hit feedback, and trigger a break response when the condition is exhausted. |
| Counters, clashes, and scripted finishers | Detect the recipient's state when contact occurs, suppress normal resolution, and transfer control to a custom counterattack, weapon clash, throw, cinematic hit, or synchronized animation sequence. |
| Per-attack custom resolution | Route tagged attacks into creator-defined combat logic while leaving all untagged attacks under native OpenBOR handling. |
Timing-Based Parry
Street Fighter III-style timing defense is a direct example. Input or key scripts record a block press and open a very short parry window on the entity. When an attack arrives during that window, the recipient's Ondoattack script clears lasthitc, starts the parry response, and spawns the flash at the recorded impact position.
void main()
{
void self = getlocalvar("self");
if(getlocalvar("which") != openborconstant("EXCHANGE_RECIPIENT"))
{
return;
}
if(parry_window_open(self))
{
changeopenborvariant("lasthitc", 0);
play_parry_response(self);
spawn_parry_flash(
openborvariant("lasthitx"),
openborvariant("lasthity"),
openborvariant("lasthitz")
);
}
}
parry_window_open(), play_parry_response(), and spawn_parry_flash() represent project-defined helpers. The important operation is the combination of detecting a valid recipient-side window, clearing lasthitc, and supplying custom feedback. With confirmation left cleared, native damage never occurs, yet the creator still has the exact collision context needed for animation, sound, flash placement, attacker recoil, meter gain, or related effects.
Execution Order
For an ordinary candidate impact, OpenBOR performs the relevant operations in this order:
- Confirms collision and preliminary attack eligibility.
- Initializes the shared last-hit context with confirmation enabled.
- Runs the recipient's
ondoattackscriptwithwhich = EXCHANGE_RECIPIENT. - Runs the conferring entity's
ondoattackscriptwithwhich = EXCHANGE_CONFERRER. - Continues only when the shared last-hit confirmation remains enabled.
- Applies remaining identity, ownership, projectile, block, counter, and take-damage rules.
- Runs later events, including Didblockscript or Didhitscript, when their conditions are met.
Both Ondoattack scripts operate on the same last-hit context. The recipient runs first, the conferrer runs second, and the value remaining after the second callback controls native continuation. Setting lasthitc to 0 in the recipient's script does not prevent the conferrer's script from running.
Exchange Roles
The which local identifies the current entity's role in the proposed exchange.
| Constant | Numeric value | Current self
|
Execution order |
|---|---|---|---|
EXCHANGE_RECIPIENT
|
1
|
Recipient or defender | First |
EXCHANGE_CONFERRER
|
0
|
Attacker, projectile, or other entity conferring the attack | Second |
Comparing which with named constants is clearer and safer than relying on raw numeric values.
Local Variables
| Local variable | Type | Value |
|---|---|---|
self
|
Entity pointer | Entity whose Ondoattack script is currently running. |
other
|
Entity pointer | Other participant in the exchange. Recipient scripts receive the conferrer; conferrer scripts receive the recipient. |
damage
|
Integer | Raw attack.damage.force at the time of the event. This is not final HP loss.
|
drop
|
Integer | Raw attack.reaction.fall.force - the attack's knockdown force.
|
attacktype
|
Integer | Internal attack-type identifier. Compare it with named ATK_* constants from openborconstant().
|
noblock
|
Integer | Raw attack.block.penetrate value. The local retains its legacy name.
|
guardcost
|
Integer | Raw attack.block.cost value.
|
jugglecost
|
Integer | Attack's juggle-point cost. |
pauseadd
|
Integer | Raw attack.reaction.pause.time in logical clock ticks.
|
tag
|
Integer | Creator-defined attack.tag metadata.
|
which
|
Integer | Current exchange role: EXCHANGE_RECIPIENT or EXCHANGE_CONFERRER.
|
attack_id
|
Integer | Current outgoing attack ID used by native duplicate-hit control. Both callbacks receive the same value. |
attack_id is not an entity ID or collision-box index. Synthetic landing-damage events may supply 0. The local name includes the underscore; attackid is not the current event local.
Numeric attack locals are copied from the current attack when each callback begins. Assigning new values to these local variables does not write them back to native attack data. Use the last-hit interface when native context must be changed.
Cancelling Native Hit Handling
The shared OpenBOR variant lasthitc exposes the current last-hit confirmation flag. Collision setup normally initializes it to 1. Setting it to 0 during Ondoattack tells the engine to abandon native handling after both callbacks finish.
The parry example above uses this behavior to replace native resolution with a creator-defined result. The same operation can power immunity, absorption, reflection, armor, scripted damage, counterattacks, and other systems. Cancellation prevents the normal block, counter, damage, and Didhit processing for that candidate. Custom effects or responses may be performed before clearing the flag.
The conferrer's callback still runs after the example clears lasthitc. If that later callback sets the flag back to 1, native handling resumes. Projects that cancel from the recipient side should establish a clear convention for conferrer scripts.
Returning 0, 1, or another value from main() does not accept or cancel the impact. OpenBOR ignores the script return value for this event.
Shared Last-Hit Context
Ondoattack scripts may inspect the full candidate through OpenBOR's last-hit variants.
| Variant | Purpose |
|---|---|
lasthitc
|
Reads or changes candidate confirmation. |
lasthit_attack
|
Provides the current attack object for property access. |
lasthit_attacker
|
Provides the entity conferring the attack. |
lasthit_target
|
Provides the proposed recipient. |
lasthitt
|
Reads or changes the current attack type. |
lasthitx, lasthity, lasthitz
|
Provide the recorded impact coordinates. |
Read these values with openborvariant(). Writable variants may be changed with changeopenborvariant().
Since the recipient callback runs first, native context changes it makes can be visible to the conferrer's callback. For example, changing lasthitt in the recipient callback changes the attack type used to populate the later conferrer's attacktype local. Treat such changes as shared state rather than private per-entity data.
Combining Both Roles
Recipient-side logic naturally handles defenses, immunity, armor, shields, and reactions. Conferrer-side logic naturally handles attack augmentation, resource costs, projectile behavior, conditional effects, and responses to the target. Projects may use both callbacks together for mechanics that belong to neither participant alone.
Both roles receive the same attack_id and shared last-hit context, allowing them to coordinate one proposed impact without searching for the other entity or repeating collision detection. Role checks keep a shared script reusable across attackers, recipients, projectiles, hazards, and other model types.
Damage on Landing
Configured landing damage also passes through Ondoattack processing. OpenBOR builds a temporary attack, chooses a living opponent as the conferrer when possible, and otherwise uses the landing entity itself. The recipient callback runs before the conferrer callback, following the same confirmation rules as an ordinary candidate impact.
Landing events use a synthetic attack context. Some values, including attack_id, may therefore be 0 or differ from ordinary attack-box collisions.
Global Model
Unlike Didhit and Onspawn, ondoattackscript is not automatically forwarded through a model named global_model. Only the scripts belonging to the two participating entities run. Shared project-wide behavior should be placed in a common script included by participating models or called from their individual Ondoattack scripts.
Related Events
| Event | Entity | Relative purpose |
|---|---|---|
| Ondoattackscript | Recipient first, then conferrer | Intercepts a candidate impact before native block, counter, and damage handling. May cancel native processing through lasthitc.
|
| Takedamagescript | Recipient | Runs while native damage and reaction handling are being applied. |
| Didhitscript | Conferrer | Runs after OpenBOR accepts a normal hit, block, qualifying item event, or landing-damage event. |
| Didblockscript | Defender | Runs after a successful native block and applicable guard-cost deduction. |