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.

Source: module-kits:kits/combat_abilities/usage/en.mdView Markdown source

Also read offline — CLI: grip-forge docs combat-abilitiesMCP tool: gripforge_docs { "topic": "combat-abilities" }

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 #

SettingDefaultMeaning
packempty packThe abilities and statuses (see below). Moddable.
inputBindingsnoneUp 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.
globalCooldownFrames0Fixed 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 reason unknown_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) and globalCooldown(source): what is left.
  • pack() and packRevision(): the pack and its revision number.
  • connect(port): use another backend than combat.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 } with phase one of windup, active, hit, recovery, ended, cancelled. target only comes with hit. The kit listens to entity.despawned and combat.killed to end casts.

Hooks and modding #

HookPayload (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 reason of use (busy, cooldown, global_cooldown, conditions for a missing or blocking tag, resource, paused, or a tap of ability.cast).
  • A held key casts only once: by design; release and press again.
  • connect fails 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.