Jump to content

Ondoattackscript

From OpenBOR

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:

  1. Confirms collision and preliminary attack eligibility.
  2. Initializes the shared last-hit context with confirmation enabled.
  3. Runs the recipient's ondoattackscript with which = EXCHANGE_RECIPIENT.
  4. Runs the conferring entity's ondoattackscript with which = EXCHANGE_CONFERRER.
  5. Continues only when the shared last-hit confirmation remains enabled.
  6. Applies remaining identity, ownership, projectile, block, counter, and take-damage rules.
  7. 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.

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.

See Also