Abilities
Give players, enemies and bosses reusable abilities with costs, cooldowns, conditions, a cancellable windup, active and recovery phases, bounded targeting and effects, driven by a data pack.
Abilities and skills (combat.abilities) #
What it is for #
combat.abilities runs the abilities of any actor: a player skill, a caster enemy, a shrine, a boss move. An ability is data in a pack: a cost, a cooldown, tag conditions, a windup, an active window, a recovery, a bounded target shape and a list of effects. One controller per actor (created on first use) walks a cast through its phases, takes the cost, starts the cooldown and tests the targets once, nearest first, in the active window. Damage, healing and statuses go through combat.core (see the combat guide). Animation, sound and effects are not played here: every phase is announced as an ability.cue event that carries the presentation names the ability declares. The kit makes no humanoid or player-only assumption.
Turn it on #
gripforge_gamekit_install { project_id: "gkp_…", id: "combat.abilities" }
gripforge_gamekit_configure { project_id: "gkp_…", id: "combat.abilities",
config: { "globalCooldownFrames": 0, "pack": { … }, "inputBindings": [ … ] } }
It requires game.engine and uses combat.core when installed. The use_ability verb casts an ability from a script or a rule.
First example #
<!-- test: kits/game_engine/host/tests/player_actions.rs::ability_input_uses_existing_cooldown_and_does_not_repeat_when_held --> A pack with one ability bound to a key. Pressing KeyF casts it once; holding the key does not cast again, and the cooldown (0.5 s) stays authoritative. While the game is paused the press does nothing.
{
"inputBindings": [{ "action": "pulse", "ability": "pulse", "keys": ["KeyF"] }],
"pack": {
"schema": "gripforge.abilities/1", "id": "test", "name": "Test", "statuses": [],
"abilities": [{
"id": "pulse", "name": "Pulse", "cooldown": 0.5,
"timing": { "windup": 0.1, "active": 0.1, "recovery": 0.1 },
"targeting": { "shape": "circle", "range": 3, "team": "enemy", "maxTargets": 8 },
"effects": [{ "type": "damage", "amount": 30, "knockback": 2 }]
}]
}
}
Settings and defaults #
| Setting | Default | Meaning |
|---|---|---|
pack | empty pack | The abilities and statuses (see below). Moddable. |
inputBindings | none | Up to 16 player buttons { action, ability, label?, keys?, buttons? }. A press edge casts the ability through the same controller; costs, cooldowns, targeting and pause still apply. |
globalCooldownFrames | 0 | Fixed simulation frames (0 to 3600) between two successful casts of one actor. |
The ability pack #
schema: "gripforge.abilities/1", an id, a name, then abilities and statuses (the same status entries as combat.core). An ability has: id, name, tags, requires (tags the caster must carry), blocks (tags that forbid the cast), cooldown (seconds), cost { resource, amount } (taken from the caster resources), timing { windup 0.3, active 0.15, recovery 0.3 }, targeting { shape: self | arc | circle | line, range 2, arc 90, width 1, offset 0, height 3, maxTargets 16, team: enemy | ally | any, follow }, 1 to 8 effects and a presentation { animation, animationAsset, vfx, sound, socket }. An effect is damage, heal or status (with knockback on damage), or the spatial smoke, wall and teleport.
An instance placed by hand gets its starting resources from the abilities component, for example { "resources": [{ "id": "mana", "amount": 100 }] }.
Services #
Service combat.abilities (the first argument is always the casting entity):
use(source, abilityId, yaw?): cast; answers{ ok: true }or{ ok: false, reason }with the reasonunknown_ability,dead,busy,global_cooldown,cooldown,unbound,conditions,invalid_aim,invalid_target,resource,paused, or the reason of a tap.useTarget(source, abilityId, target, yaw?): cast at a named target; its team, range and life are checked again.cancel(source, force?): cancel the running cast;cancelTarget(target)cancels the casts aimed at a target.cooldown(source, abilityId)andglobalCooldown(source): what is left.pack()andpackRevision(): the pack and its revision number.connect(port): use another backend thancombat.core(a capability another kit serves,{ service, methods }).
Verb: use_ability { instance | entity, ability, yaw? } (default entity: the player).
Events #
ability.cue:{ source, ability, cast, phase, target?, presentation }withphaseone ofwindup,active,hit,recovery,ended,cancelled.targetonly comes withhit. The kit listens toentity.despawnedandcombat.killedto end casts.
Hooks and modding #
| Hook | Payload (mutable first) | Effect |
|---|---|---|
ability.cast | { source, ability, yaw } (yaw) | Before the checks; cancel it to refuse the cast (a silence); nothing is spent. |
ability.spend | { source, ability, resource, amount } (amount) | The cost about to be taken; cancelled or insufficient: refused with reason resource. |
ability.apply | { source, target, ability, scale } (scale) | One target about to be hit; scale multiplies damage and heal; cancel to skip it. |
ability.cooldown | { source, ability, cooldown } (cooldown) | The cooldown about to start; not cancellable. |
pack and inputBindings are moddable, and so is the abilities collection.
Engines #
Web and the native player run the Rust implementation. Godot, Unity and Unreal receive the controller as files: bind the project combat and presentation callbacks and validate in the destination project.
When it does not do what you expect #
- A cast is refused: read the
reasonofuse(busy,cooldown,global_cooldown,conditionsfor a missing or blocking tag,resource,paused, or a tap ofability.cast). - A held key casts only once: by design; release and press again.
connectfails on the native host: pass{ service, methods }, not an object of functions.
Limits #
No animation or sound playback, no pathing. The spatial effects smoke, wall and teleport need a bound placement port and refuse before spending when none is bound.
Versions #
Kit 1.8.0 (explicit useTarget, globalCooldownFrames), 1.6.0 (inputBindings), 1.3.0 (ability.cooldown), 1.2.0 (hooks). Requires game.engine 1.