Ability hooks and scope reference¶
This reference describes the ability hooks and their callers. The definition and identity notes were updated on 2026-11-02; check source before changing other hook details. The test records describe observed checks and their limits. These checks do not certify every hook, ability, stage or player build.
How the pieces connect¶
- A character, upgrade or player binding provides an internal key.
AbilityDefinitionLookupresolves theAB_definition, which creates a behavior for that player. - The runtime creates a scope and calls the behavior's
Connectmethod. The behavior registers the hooks it needs; each player has a separate hook hub. - Gameplay publishes an event, requests a value change or asks for a decision. The hub calls the registered methods in order.
- Removing the last entry using a definition class, or disposing the runtime, disposes the scope. This removes its subscriptions and runs cleanup.
The runtime stores mutable PlayerAbilityState by internal definition key and groups behaviors and scopes by concrete AB_ class. Reacquiring the same definition reuses that player's state and resets its cooldown; other players have separate states and behaviors. Activation chooses the first acquired entry of the requested class. Some abilities have separate definition keys on the same class, including Concussive/Deadly Impact and Super Magnet/GKCrown. If a runtime has not been created, gameplay callers generally skip its hooks.
Authoring and startup¶
- Put metadata and behavior in an attributed, preserved
AB_class. Use named values such asname,cooldownandstaminaCost; omitted numeric values default to zero. Preserve existing four-character upgrade codes; Dash and automatic character abilities can omit one. - After a new definition compiles, use Tools > Tiny Chaos > Register Ability IDs. It generates a stable ID in the separate registry; do not edit the generated record by hand. Keep a script's
.metafile with it when moving or renaming it. For multiple definitions on one class, keep the variant labels stable. - Keep reward identity, icon and upgrade values in the existing Upgrade asset. Its ability key selects the code definition; an upgrade ID is not an ability key.
- Store player-specific counters, charges and timing on the behavior or
PlayerAbilityState, never on a shared definition. Register only the hooks the behavior uses, and clean up subscriptions and owned effects when it is removed. - Released scenes use
GameInitialization.PreparePlayer: await character weapon/stat setup, initialize the player's ability host with prefab starting keys, acquire the character definition, then set dash speed and register camera/UI ownership.Player.Updatepolls scene Dash input;PlayerAbilityHost.Updatepublishes ability ticks. - The separate development run participant has its own preparation path. Checks using its synthetic fixtures do not establish that the released scene path works.
FixtureAbilitiesis compatibility data for older checks, not the production starting list.
Scope operations¶
The implementation is in PlayerHookHub.cs.
| Ability-side method | Handler form | Use | Caller-side operation |
|---|---|---|---|
scope.Listen(key, handler, order, id) |
void Handler(T context) |
React to a notification. All subscribers in the stage run in order. | hub.Publish(key, context) |
scope.Modify(key, handler, order, id) |
T Handler(T value) |
Change a value, such as dash cost. Each handler receives the previous result. | hub.Modify(key, value) |
scope.Modify(key, handler, order, id) |
TValue Handler(TContext context, TValue value) |
Change a value while receiving extra context, such as incoming damage and its hit information. | hub.Modify(key, context, value) |
scope.Decide(key, handler, order, id) |
bool Handler(T context) |
A true result handles/accepts the stage and stops later handlers. The meaning of true depends on the specific hook below. |
hub.Any(key, context) |
scope.OnDispose(cleanup) |
void Cleanup() |
Release or reset behavior-owned state when the scope is disposed. | Runtime disposes the scope. |
order is required in the current API: choose a group from 0 to 99. Lower groups run first and 99 is last; multiple handlers may share a group. Within the same group the current implementation sorts by behavior class's full name and subscription id, using ordinal comparison. This is deterministic for unchanged names but is not yet a durable identity. Use different groups if the order matters. An ability may register several hooks or multiple uniquely named subscriptions; the same behavior identity and id cannot be registered twice on the same key. The scope owns the resulting registrations; an ability should not create its own scope.
Each call takes an ordered snapshot. Registration/removal during a dispatch takes effect on the next dispatch, including a reentrant call; handlers already in the current snapshot may still run. A handler exception stops the dispatch and propagates. OnDispose callbacks and registrations are disposed in reverse registration order; cleanup attempts all entries and reports exceptions afterward. On final removal, the runtime separately cancels work and cooldowns started for that ability script type. Disposing the scope alone does not cancel work, and already-created attack effects remain on their attacks. General player-owned work started with Run is not tied to an individual ability.
Example from AB_SurvivalFitness.cs:
public void Connect(PlayerHookScope scope)
{
scope.Modify(MovementAbilityHooks.DashCost, ModifyDashCost, 0, "dash-cost");
scope.Listen(CombatAbilityHooks.DamageReaction, OnDamageReaction, 0, "damage-reaction");
}
public float ModifyDashCost(float cost) => cost - 5;
public void OnDamageReaction(PlayerDamageHit hit) => player.currentStamina += 20;
The number is the priority group, not an absolute position. The string is a subscription identifier, not the ability's display name.
Hook list¶
Listen, Modify, and Decide in the tables below refer to the ability-side scope method. They are not all interchangeable: check the input and result before choosing a stage.
Movement¶
Defined in MovementAbilityHooks.cs; called by the scene dash adapter and movement-driver dash controller.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
BeforeDash |
Listen | PlayerDashFeedback |
After dash admission and stamina check, before launch. |
DashCost |
Modify | float → float |
When dash stamina cost is calculated, before checking available stamina. |
Player damage and combat¶
Defined in CombatAbilityHooks.cs; called by PlayerDamageHooks.cs, except thorn retaliation, which Electrical Thorn Armor publishes.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
BeforeImmunity |
Listen | PlayerDamageHit |
Before the damage resolver checks invulnerability. |
AbsorbShield |
Decide | PlayerDamageHit → bool |
After immunity; true means this shield absorbed the hit, so later shields do not run. |
HealthDamage |
Modify | PlayerDamageHit, float → float |
After the general damage-taken calculation, before health subtraction. |
DamageReflection |
Decide | PlayerDamageHit → bool |
During damage reactions; current reflection abilities return true after handling so only the first runs. |
DamageReaction |
Listen | PlayerDamageHit |
After health-damage handling and damage reflection, before lethal resolution. |
PreventDeath |
Decide | Player → bool |
At lethal resolution; true means death was prevented/handled. |
ThornRetaliation |
Listen | Player |
Published by Electrical Thorn Armor's retaliation effect; currently used by Judgement. |
PlayerDamageHit contains the amount, source entity, attack source, knockback, damage type and knockback direction. The damage resolver has additional general steps outside these hooks, including checking immunity and applying invulnerability.
Pickups¶
Defined in CollectibleAbilityHooks.cs; called by PlayerCollectItem.cs.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
EffectCount |
Modify | int → int |
Calculates how many times applicable collectible effects run. |
ChestCollected |
Listen | ICollectible |
When a normal or boss chest is handled, before rewards are enqueued. |
CoinCollected |
Listen | ICollectible |
After the coin's gold, experience and collection count are applied. |
PotionCollected |
Listen | ICollectible |
Once when potion handling begins, before its effect loop. |
PotionApplied |
Listen | ICollectible |
For each application of a regular or greater potion after its resources are added; it is not called for every potion subtype. |
Companions¶
Defined in CompanionAbilityHooks.cs.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
AttackPrepared |
Listen | CompanionAttackContext |
During Companion.AttackModify, before the companion's damage and SpellAmp assignments. Context has Companion and Attack. |
AttackInterval |
Modify | Companion, float → float |
During the drone's attack-interval calculation. |
ExtraCompanionsRequested |
Listen | Player |
Multishot publishes this after an upgrade equips it; character abilities decide how to add companions. |
Not every companion uses the same attack preparation path. Sword and orb attacks can go through PlayerWeapon.AttackModify; trace the specific caller before assuming this stage covers them.
Weapons and attack preparation¶
Defined in WeaponAbilityHooks.cs; mostly called by PlayerWeapon.cs. The three attack-preparation stages are separate because spell amplification, critical selection and damage calculation occur between them.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
Started |
Listen | PlayerWeapon |
Weapon startup. |
Destroyed |
Listen | PlayerWeapon |
Weapon destruction. |
Reload |
Listen | PlayerWeapon |
At the existing reload stage before reload state/audio processing. |
HoldFire |
Decide | WeaponChargeContext → bool |
Before a shot; true holds fire. Context contains Weapon and DeltaTime. |
BeforeFire |
Listen | WeaponFireContext |
Before projectile spawning. Context contains Weapon and FireWeapon; this stage also runs for the existing activation-only call. |
ShotCompleted |
Listen | PlayerWeapon |
After the weapon's normal fire call completes. |
ExtraShots |
Listen | PlayerWeapon |
After the main projectile is spawned. |
BeforeSpellAmp |
Listen | AttackPreparationContext |
During weapon attack preparation, before SpellAmp is assigned. Context contains Weapon and Attack. |
BeforeCriticalRoll |
Listen | AttackPreparationContext |
After SpellAmp assignment, before the critical roll. |
GuaranteedCritical |
Decide | PlayerWeapon → bool |
Critical selection; true guarantees a critical. Soul Wager also consults it. |
ForcedCritical |
Decide | PlayerWeapon → bool |
Critical selection; true forces a critical. Soul Wager does not consult this stage. |
BeforeCriticalReset |
Listen | AttackPreparationContext |
On a critical, before the guaranteed-critical reset. |
CriticalResolved |
Listen | PlayerWeapon |
On a critical, at the guaranteed-critical reset stage. |
AfterCriticalReset |
Listen | AttackPreparationContext |
On a critical, after that reset. |
BaseDamage |
Modify | PlayerWeapon, float → float |
During weapon damage calculation, before fixed damage and critical calculation. |
Damage |
Listen | WeaponDamageContext |
After critical/noncritical damage calculation. Context has Weapon, Attack, and CriticalRoll; handlers may change the attack. |
AfterDamage |
Listen | AttackPreparationContext |
After the Damage stage during attack preparation. |
CarrierAttackPrepared |
Listen | PlayerAttack |
When PlayerAttackCarrier creates its explosion attack. |
Excavation |
Listen | Player |
When Ground Breaker triggers its excavation event; currently used by Lucky Find. |
Player progression¶
Defined in PlayerProgressionHooks.cs; called by PlayerManager after scheduling a level-up reward and before increasing the level. Each phase is sent to each player separately.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
BeforeLevelUp |
Listen | Player |
First level-up phase; Future Pills applies its limited permanent stat additions here. |
LevelUp |
Listen | Player |
Second phase; RPG Buff applies recovery and one random stat bonus. |
Upgrade values¶
Defined in UpgradeAbilityHooks.cs; called by UpgradeFunction before applying an additive stat upgrade. A player ability runtime must already be prepared.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
AdditiveValue |
Modify | Upgrade.UpgradeList, float → float |
For additive damage, attack speed, critical damage/chance, reload, movement, ammo, health, stamina and skill amplification upgrades. |
Adept Learner runs first, then Mage Apprentice, Proto Barrier, Ground Breaker, Rocket Barrage and Sky Ying Sword in that order. Each changes only its applicable upgrade values. This does not replace the central calculation of final player stats.
For a one-time ability-specific upgrade operation, a definition can implement IAbilityUpgradeOperation. UpgradeFunction calls its ApplyUpgradeOperation(player, operation, value) at that operation's position in the upgrade list. Return true when handled. This is separate from the player hook scope and can run before the ability is equipped, as Karmayana's existing upgrade requires.
Reward card count¶
Defined in RewardAbilityHooks.cs. CardCount lets an acquired player ability modify the default of three cards for special and boss rewards. The highest player result is used. No current ability subscribes; Quartet remains unfinished development content.
Health recovery¶
Defined in HealthRecoveryHooks.cs; called by Player.RegenerateHealth.
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
BlockRecovery |
Decide | Player → bool |
Before a potion is consumed; true blocks this recovery cycle. |
BlockDirectHealing |
Decide | Player → bool |
Checked by direct healing effects; true blocks healing. RPG Buff retains its existing Last Stand exception. |
Contributions |
Listen | HealthRecoveryContext |
After potion consumption; vitamin and petrification bonuses are added here. |
AfterContributions |
Listen | HealthRecoveryContext |
Adds Survival Instinct recovery, then Meditate recovery. |
The context holds the recovery amount. AddOnce lets related effects share one contribution: Petrified Skin and De-Petrify use the same key so they add their bonus only once per cycle.
Ability lifecycle¶
| Hook | Scope | Input / result | When it runs |
|---|---|---|---|
AbilityLifecycleHooks.Tick |
Listen | Player |
The active player ability runtime publishes it each frame. The owner/runtime lifecycle controls whether it is published. |
AbilityLifecycleHooks.Equipped |
Listen | PlayerAbilityState |
Published after an upgrade equips an ability, including a queued equip. Starting abilities and ordinary acquisitions do not publish it. |
Defined in AbilityLifecycleHooks.cs and published by PlayerAbilityRuntime.cs.
Effects attached to individual attacks¶
These are a separate mechanism from PlayerHookScope and the player-level hook list. An ability can attach an impact handler to a specific PlayerAttack:
attack.AddHitEffect("freezing-pendant", 0, Freeze, true, true);
The arguments are: effect ID, priority group, handler, whether it transfers to child projectiles, and whether it transfers to explosions. The last two arguments default to false. The handler receives PlayerAttack, Enemy and the hit position. Effects are sorted by group then ID, run on enemy impact, and are cleared when the pooled attack resets. An attached effect can remain on an already-created attack after its ability is removed. CopySecondaryHitEffectsTo copies only effects whose transfer setting permits the destination.
attack.AddDamageEffect(id, order, handler) attaches an ordered damage modifier for a particular attack. Its handler receives the enemy and PlayerAttack.HitDamage, then returns the updated value. AB_ProtoBarrier is the current example. The attack effect lists are separate from the player hook hub; a combined ordering between them has not been implemented.
Some handlers still read live player or attack values at hit time. Captured projectile ownership, pooling and removed/destroyed-player cases are source-level work and need later Unity verification.
If an ability needs a new hook¶
First check the lists above for a stage with the correct timing and input. If none exists, the gameplay owner must declare a typed key beside its other hooks and call the hub at the point where the event actually occurs. Then the ability can subscribe in its own Connect method. Do not add an ability-name branch or a separate forwarding method to PlayerAbilityRuntime for the new hook.