Skip to main content

Define an Ability

An ability defines one generic skill: an active, passive, triggered, targeted, channelled or interval skill of a player or an artifact, an aura effect, a storage cell, a flight toggle and so on. What it does is written in the action fields; what it costs and when it may run are written in the declaration fields.

This tutorial adds two abilities to the example pack:

  • an active bolt cast from the wheel;
  • a triggered recovery that answers damage.

What You Are Building​

FilePurpose
data/example/saturn/ability/qi_bolt.jsonThe active bolt: costs, cooldown and condition, running its own four action fields on the press.
data/example/saturn/ability/qi_recovery.jsonThe triggered recovery: it reacts to being hurt and runs its own action fields once the trigger fires.
data/example/mxt/realm_stage/foundation.json(edited) grants the bolt on breakthrough.
data/example/mxt/item_binding/root_pellet.json(edited) also grants the recovery ability.
data/example/mxt/technique/azure_breath.json(edited) grants both once learned.

Step 1 — An Active Ability​

// data/example/saturn/ability/qi_bolt.json
{
"type": "mxt:active",
"icon": "example:textures/gui/ability/qi_bolt.png",
"costs": [
{"type": "mxt:resource", "resource": "example:qi", "amount": 10}
],
"cast_time": 10,
"cooldown": 40,
"condition": {"type": "mxt:has_realm", "aura": "example:qi"},
"target_selector": {"type": "mxt:area", "radius": 6, "include_actor": false},
"bi_entity_action": {
"type": "mxt:target_action",
"action": {"type": "mxt:damage", "amount": "6 + caster_level * 0.5"}
}
}

The fields this ability reads (cooldown and the four action fields below it are not fields shared by every ability - the first is read only by a type that pays, the others are declared only by a type that runs actions):

FieldWhat it does
typeSelects the lifecycle from the built-in ability_type registry; every type is on Ability Types.
iconOptional. A bare string is a 16x16 GUI texture; an object is an item stack template ({"id": ...}, optionally count and components). Without it the entry is drawn with its name.
costsA list of Cost objects, paid before the behaviour runs, all or nothing as one array. The four entries and the {"id": ..., "amount": ...} shorthand are on Cost Types.
cast_timeCast duration in ticks.
cooldownCooldown in ticks, reported back to the client so the wheel can lay the cooldown sheet over that sector's icon and say "On cooldown 4.3s" - the seconds left, always one decimal.
conditionAn entity condition that must pass before the ability can be used. Its only job here is to keep Mortals from throwing bolts. For a passive mxt:modifier or mxt:aura ability the same field keeps working after the cast: it is re-checked every tick and the passive effect is withdrawn while it fails.

mxt:active also declares these four action fields itself - they are not fields shared by every ability; only the types that run actions declare them:

FieldWhat it does
target_selectorWhich entities the bi-entity behaviour applies to; mxt:self (the default) selects only the caster. The shape of every selector and their limit and order are on Ability Target Selectors. The example uses mxt:area - a box, not a sphere - and include_actor decides whether the caster counts as part of the set.
bi_entity_actionRun for each selected target, and a failing one never stops the rest. mxt:target_action forwards an entity action to the target — here 6 damage plus half the caster's experience level.
entity_actionRuns on the caster. It defaults to mxt:no_op; use it for a self-buff, a particle burst or an aura change.
target_conditionThe bi-entity condition every target has to pass; mxt:always by default.

The four action fields always run in this order: entity_action first, then target_selector picks targets, then each target is tested by target_condition, and only then does bi_entity_action run. The full per-type timing table is on Ability Types · The Four Action Fields.

A few more fields are worth knowing about:

  • charges is the only state parameter left on the definition: writing {"maximum": ..., "recharge_ticks": ...} (both required) makes the ability pay per use — one charge per payment, refused at zero (failure reason NO_CHARGES) — and the remaining count is state rather than a declaration. Only a payment on the cast pipeline spends one; a key going through the shared gate (mxt:flight_control / mxt:storage) spends no charge. Which state kinds can be held is declared by the type in code, so there is no components to write any more (writing one is silently ignored like any unknown key); see Ability Casting for the kinds and the conditions that read them.
  • Element affinity is not written on the ability: it is its own data pack registry, see ability_affinity. Once an ability is named there it is both a cast gate and part of the formula variable damage_multiplier, so you neither write nor multiply an affinity factor by hand.
  • hidden is skipped only in an artifact's tooltip and is still granted and still works; it takes no part in the wheel's filter (the pool only offers pressable types), so a pressable ability with it still takes a wheel cell.
Where realm ranks come from

An ability formula runs in an entity context. It provides caster_health, caster_max_health, caster_level (the vanilla experience level), caster_<resource> and caster_<attribute> — but not realm_rank, which only exists in formulas evaluated for one resource. Use caster_example_qi when an ability should scale with the caster's aura.

Step 2 — A Triggered Ability​

A triggered ability fires when the world does something to its owner. The trigger also injects a few variables that describe what happened. Its own part is "which signal, how likely", and what runs is written in its own action fields:

// data/example/saturn/ability/qi_recovery.json
{
"type": "mxt:triggered",
"triggers": [{"type": "mxt:hurt"}],
"chance": 1,
"cooldown": 100,
"condition": {"type": "mxt:has_realm", "aura": "example:qi"},
"costs": [
{"type": "mxt:resource", "resource": "example:qi", "amount": 5}
],
"entity_action": {"type": "mxt:heal", "amount": "2 + damage * 0.5"}
}
  • triggers is the list of built-in matchers; each entry names a type id and carries no fields of its own. Every entry is on Triggers.
  • chance is a number provider, 1 by default, and it is rolled once per matching trigger: a formula that throws or does not evaluate to a finite number means not allowed, <= 0 is not allowed, >= 1 always is, and anything in between rolls the entity's random once. Do not confuse it with the chance of the same name in the saturn/trigger rule table — there a value that cannot be computed counts as 1.
  • The hurt trigger adds damage — the damage actually inflicted — which is why the heal can scale with the hit; variables injected by other triggers are just as readable to this ability's own action fields, and the table is on Formula Variables.

The full variable table is in Formula Variables.

Step 3 — Granting the Abilities​

Defining an ability does nothing on its own: an entity has to hold it. The mxt:grant_ability entity action does that, and its source field records who granted it.

From a realm. Edit the realm the player reaches:

// data/example/mxt/realm_stage/foundation.json
"success_action": {
"type": "mxt:grant_ability",
"ability": "example:qi_bolt",
"source": "example:foundation"
}

success_action runs on the stage the player just entered, so reaching Foundation Establishment teaches the bolt. ability_requirements on a stage is the mirror image: it lists abilities that must already be held before the breakthrough is allowed.

From an item. Write the action into the item_binding definition that claims the item:

// data/example/mxt/item_binding/root_pellet.json
{
"items": "kubejs:root_pellet",
"actions": [
{"type": "mxt:grant_spirit_root", "spirit_root": "example:fire_root"},
{"type": "mxt:grant_ability", "ability": "example:qi_recovery", "source": "example:root_pellet"}
]
}

From a spirit root, physique or technique. Those definitions have a granted_abilities list that is applied while they are held:

// data/example/mxt/technique/azure_breath.json
"granted_abilities": ["example:qi_bolt", "example:qi_recovery"]
Source identities

Keep source stable and meaningful — the definition ID that granted the ability is a good choice. Abilities from different sources are tracked separately, and a source is what makes a later removal traceable, so two items can grant the same ability without one of them silently revoking the other.

Step 4 — Putting the Ability on the Wheel​

Active abilities can be put on the twelve-sector wheel that abilities and spirit power share (a main wheel plus pages read from what you carry):

  1. Type /wheel in chat (the client command, the same as the "Open Wheel Configuration" key, unbound by default) to open the wheel editor: six columns of spirit power on the left, six columns of the skills you have learned and of the skills the artifacts in your Curios slots declare on the right (a skill only a main-hand or off-hand item declares is not there - it only appears on the pages read from your gear), and one row of twelve shared cells underneath which are the main wheel's twelve cells (1 is straight up, counting clockwise). Left-click the bolt in the right pool to pick it up, then click a cell to put it there, and press Escape to save and close. A cell you deliberately leave empty stays empty, and a saved cell whose definition no longer exists shows a red ? and is never replaced by something else.
  2. Hold "Wheel Menu" (R by default) and point at that cell - pointing is what changes the selection, which is never empty: the wheel opens with the first cell that holds anything already selected (that is where the gold frame sits before you move the pointer), and pointing at an empty cell leaves the selection where it was. Letting go of R only closes the wheel and casts nothing. Press "Use Wheel Selection" (V by default) to cast it, and the wheel stays open so you can move to another cell and press it again; with the wheel closed V spends the cell the number you chose stands for right now (a number addressing a page that is gone falls back to the last cell holding anything, and is never rewritten), and a left click is the same as V. The "Wheel Grid" on the left of the screen is a four-column view of the whole wheel, one row per three cells of a page, and the cell outlined in gold is what V would spend.
  3. Everything is server-authoritative: the client only sends which kind and which id was used, and the server decides the grant, the conditions, the costs, the cooldown, the duration and the effects.

Verify​

Abilities are a data pack registry, so load the world again rather than running /reload:

(load the world again)
/mxt registries validate → no codec errors
/mxt attachment status → lists the abilities the entity holds
/saturn ability cast example:qi_bolt → forces the cast (gamemaster permission)
  1. Before entering the chain, /saturn ability cast example:qi_bolt fails: the condition rejects it.
  2. Break through to Foundation Establishment and check /mxt attachment status. The bolt is now held, and source shows it came from the realm.
  3. Cast it from the wheel (hold R, point at the sector you put it in, then press V). 10 qi is deducted, the cooldown starts, and nearby entities take damage. Compare the value shown by /mxt resource example:qi before and after.
  4. Set the pool too low with /mxt resource example:qi set 5 and cast again: the cast is refused because the costs cannot be paid, and nothing is deducted.
  5. Take a hit with qi_recovery granted: the heal amount scales with the damage taken, 5 qi is spent, and the 100-tick cooldown prevents it from firing again immediately.

Common Mistakes​

SymptomCause
The ability never appears in the wheel's poolThe pool lists the skills that need a key; triggered and passive abilities have no wheel entry of their own.
Costs are never paidAn entry needs a type together with its own fields, such as {"type": "mxt:resource", "resource": ..., "amount": ...}, or the {"id": ..., "amount": ...} shorthand — not {"resource": ...} without a type.
An ability formula is always 0It used a variable its context does not provide, such as realm_rank in an entity formula. The name is reported at evaluation time: a development environment logs the whole error, production logs one warning line per distinct message, and both continue with 0.
mxt:word does nothingIt is a terminal, code-whitelisted effect (self_heal, purge_self_curses) and requires an operator by default. It is not a way to run commands.
Everyone has the ability immediatelyIt was granted by a granted_abilities list on a spirit root, physique or technique that everybody satisfies — those lists apply while the definition is held.
The actions never happen and nothing errorsThe four action fields were written on a type that does not run actions (mxt:modifier / mxt:mount / mxt:storage and the like). Only the five types that run actions read them, so on any other type they are keys nobody reads (no error, no effect) — switch this ability to a type that runs actions, or move the keys to the ability that really runs them. The table of types and fields is on Ability Types · The Four Action Fields.
An "effect": "..." line does nothingThe ability-reference effect field is a key nobody reads: write the four action fields on the firing type itself and leave the effect line out; the effect of mxt:word is a different thing (its own effect enum) and is unaffected.
A targeted skill only says "no target matches" when pressedThe target_selector of the mxt:targeted ability picked no entity at all, or every pick was filtered out by the payload ability's target_condition: check the selector's distance (mxt:area's radius, mxt:ray / mxt:cone's length). Landing on nobody is decided before anything is paid, so it costs nothing; an ability that cannot act on a target reports the other reason, "the ability it names cannot act on a target".

Next​