Authoring Abilities & Ability Mods

Active abilities are a fully pack-authorable content type as of 1.6.0 - the jar's own 91 defaults are decoded through the same codec a pack file uses

Ability assets shipped
91
Active (hotbar-castable)
57
Passive / triggered
34
EffectStep kinds
23
Content pack assetServer/MMOSkillTree/Abilities/<id>.jsonAbilities/

Same codec (AbilityAsset.CODEC) decodes the jar defaults, pack files, and the owner overlay at mods/mmoskilltree/ability-overrides.json. Ability Mods live at Server/MMOSkillTree/AbilityMods/<id>.json and have no owner-override layer - see Ability Mods below.

defaults<pack<owner

The filename (lowercased at decode) is the ability id, e.g. Arcane_Missiles.json decodes to arcane_missiles. AbilityCatalog folds every decoded instance and both ActiveAbilityService and PassiveAbilityTriggers read it directly for cast dispatch - the asset is the runtime ability, not just descriptive content. A pack file with the same id as a jar default overrides it entirely unless it uses Parent (see below).

Top-level groups

Every group, and every leaf inside every group, is registered appendInherited: a Parent-linked child that overrides only Cooldown.Ms still inherits Cost, Effects, Presentation, and every other leaf from the parent. The one exception is Effects itself - see EffectStep.

Identity

FieldTypeDefaultDescription
NameKeystringoptionalDisplay name localization key.
DescKeystringoptionalDescription localization key.
IconstringoptionalAn item id whose icon renders as the ability icon.
PassiveboolfalseWhen true, fires from a combat hook (PassiveAbilityTriggers) instead of being cast on demand - skips Cooldown, Cost, and Casting gates, and the top-level Presentation never plays.
* required

Cooldown

FieldTypeDefaultDescription
MslongoptionalMilliseconds between casts.
* required

Cost (Java type CastCost)

FieldTypeDefaultDescription
StatstringoptionalResource-bar stat id charged on a successful cast, e.g. MANA, STAMINA.
AmountintoptionalAmount charged.
CostsobjectoptionalThe unified content/cost Cost shape ({"currencies":{...},"items":[...]}), embedded for a future currency/item cast cost. Not read by any runtime path today - author it if you like, but nothing charges it yet.
* required

XpRouting

FieldTypeDefaultDescription
Skillsstring[]optionalSkill ids this cast's XP and damage credit. Also accepts the "$HELD" token, which resolves to the held weapon's skill at cast time.
HeldExcludestring[]optionalSkill ids to exclude from held-weapon fallback routing.
* required

Casting

FieldTypeDefaultDescription
RequiresobjectoptionalThe unified content/gate Requirements object (features / skills / minCombatLevel / permission).
MinHealthPercentdouble (0.0-1.0)optionalCaster's health / maxHealth must be >= this.
MaxHealthPercentdouble (0.0-1.0)optionalCaster's health / maxHealth must be <= this.
HeldWeaponCategorystringoptionalCaster's held weapon must resolve to this skill id, e.g. "SWORDS".
* required
Three legacy cast predicates from earlier abilities (a required armed slot, an invulnerability check, a minimum combo-hit count) have zero live users across all 91 shipped abilities and carry no leaf here - do not assume they are authorable in a pack file.

Targeting

FieldTypeDefaultDescription
Modestringoptionale.g. SELF / PROJECTILE / RAYCAST - drives step dispatch, not a free string.
MaxDistancedoubleoptionalRange.
RadiusdoubleoptionalAOE radius when applicable.
PierceintoptionalProjectile/beam pierce count. Lives here at the ability level, not on the Projectile/Beam step leaf.
* required

Presentation

A shared type used at multiple call sites - see Presentation below for the full field reference and both scopes it appears at.

PassiveTrigger

FieldTypeDefaultDescription
Onstringoptionalon_hit_dealt | on_kill | on_finisher | dodge_armed.
ChancedoubleoptionalSchema-legal, but zero live users - every shipped passive fires at 100%.
FilterstringoptionalRestricts which source ability triggers this passive; "any" = no restriction.
RequiresWeaponSkillstringoptionalOnly fires while wielding a weapon resolving to this skill id.
SelfHpRangeMindouble (0.0-1.0)optionalCaster HP fraction gate, lower bound.
SelfHpRangeMaxdouble (0.0-1.0)optionalCaster HP fraction gate, upper bound.
TargetHpRangeMindouble (0.0-1.0)optionalTarget HP fraction gate, lower bound.
TargetHpRangeMaxdouble (0.0-1.0)optionalTarget HP fraction gate, upper bound.
ComboFinisherOnlybooloptionalRestrict on_hit_dealt to combo-finisher hits only.
BonusFromAbilitystringoptionalWhen the source ability's id matches, scale a numeric param by BonusMultiplier.
BonusMultiplierdouble1.0Scale factor (1.0 = no bonus).
BonusScalesParamstringdamageMultiplierWhich param BonusMultiplier scales.
* required
A passive: heals on kill, only while an Archery weapon is held
{
  "Identity": { "NameKey": "ability.arch_passive_clean_kill.name", "Icon": "Weapon_Arrow_Iron", "Passive": true },
  "XpRouting": { "Skills": ["$HELD"] },
  "Effects": [
    { "Type": "Heal", "Presentation": { "Sound": "SFX_Health_Potion_High_Drink" },
      "Heal": { "Amount": 8.0, "IntervalMs": 1000, "DurationMs": 0, "EntityEffect": "MMO_CleanseAura", "ViaLifesteal": false } }
  ],
  "PassiveTrigger": { "On": "on_kill", "Filter": "any", "RequiresWeaponSkill": "ARCHERY" }
}

Damage schools and Cause

Every damage-bearing step leaf (Damage, Dot, Zone, Beam, an OnHit sub-step, and NativeChain) carries an optional Cause string naming a DamageCause asset id. Six ids are the mod's damage schools: Fire and Ice are vanilla; Lightning, Water, Arcane, and Void are MMO-authored, each Parent-ing the vanilla Elemental cause. A non-school vanilla cause (Physical, Slashing, Bludgeoning, Poison, ...) is equally valid. An unset or unresolvable Cause falls back to Physical silently at runtime, but AbilityValidator errors an unresolvable one at /mmoconfig validate time.

json
"Damage": { "Amount": 45, "Cause": "Ice" }

SCHOOL_RESIST is owner-config only

The SCHOOL_RESIST skill-tree reward type (a skill-tree.json choices entry, combatTarget one of the six school names) targets these same schools, but a pack cannot ship skill-tree nodes at all - resistance to a school is configured by the server owner only.

The EffectStep union

Each Effects[] entry is {"Type": "<Kind>", "<Kind>": {...}, "Presentation": {...}?, "SummaryHidden": true?}. Type picks exactly one matching nested group. Damage, Projectile, Dash, Beam, and ArmNextHit each carry their own OnHit: EffectStep[] - a hit-triggered sub-chain, recursive over the same EffectStep type. SummaryHidden: true marks a step whose mechanic is already described by a sibling step (tooltip-only, never changes runtime behavior).

No per-leaf Parent inheritance inside a step

Unlike the ability's top-level groups, EffectStep and every nested group stay plain append. The whole Effects array is inherited wholesale when a Parent child omits it entirely - it is never per-element merged. Overriding one step means restating the entire array.

Amount-type fields are double unless noted; *Ms fields are long; unmarked fields are optional/nullable.

FieldTypeDefaultDescription
TeleportstepoptionalMaxDistance; Arrive (a Presentation for the destination arrival moment, distinct from the step's own launch-time Presentation).
DamagestepoptionalAmount, Radius, Cause, OnHit[], Splash (bool - true = victim-centered secondary AOE excluding caster+primary; false/null = caster-centered self-AOE), CanCrit (default true), ArmorPenetration (0-1, reduces MMO defense-stat mitigation only), Scaling.
DotstepoptionalAmount (per tick), IntervalMs, DurationMs, Cause, MaxStacks, AllowProcs, EntityEffect, TickSound, StackKey (null disables stacking), AbilityOrigin (bool - true attributes ticks to the casting ability for XP), RayDistance (top-level-only raycast target pick), Scaling.
ApplyEffectstepoptionalEffectAsset, DurationMs, TargetVictim (must target ctx.victim), TargetCaster (force caster target even with a victim in context).
HealstepoptionalAmount, Percent (percent of the healed entity's max HP, never of damage dealt), IntervalMs, DurationMs, PerTick (regen tail), EntityEffect, ViaLifesteal (true = unconditional heal for an on-hit lifesteal child, no full-HP guard/sound; false/null = the top-level self-heal path with a full-HP no-op guard).
ZonestepoptionalRadius, IntervalMs, DurationMs, DamagePerTick, HealPerTick, DropDelayMs (telegraph window), MaxTargetDistance (default 30.0), AffectAllies, Cause, ImpactParticle/ImpactSound (first tick only), TickParticle/TickSound (every tick), MeteorProjectileAsset/MeteorDropHeight, VolleyArrowProjectileAsset/VolleyArrowsPerTick/VolleyArrowSkyHeight (all cosmetic telegraph only), AtVictim (anchors on ctx.victim instead of a ground raycast), Scaling.
ProjectilestepoptionalAsset (a ProjectileConfig id), Impact (a Presentation for the hit-location moment), OnHit[], RaycastMode (skip the physical entity, raycast instead), MaxDistance (raycast mode only, default 20.0), Count (salvo shots, default 1), IntervalMs (delay between salvo shots). Pierce is deliberately not here - see Targeting.Pierce.
DashstepoptionalDistance, IFrameMs, WindupMs, VelocityHorizontal (default 28.0), VelocityVertical (default 0.1), AirVelocityMultiplier (default 0.6), DirectionMode (LOOK default | MOVEMENT), InteractionAsset (a native RootInteraction fired instead of the Java velocity impulse), HitRadius (sweep sphere, default 2.0), TrailEffect, OnHit[] (OnHit[0] = the primary swept-entity Damage).
ArmNextHitstepoptionalMultiplier, FlatBonus, DurationMs, Slot, OnHit[] (fires when the armed hit lands), CastDamage (a Damage group, cast-time self-centered AOE), CastRay ({Distance, OnHit[]}, cast-time forward raycast run before the arm), OnHitArm (bool - the on-hit-child flavor that arms the caster's next hit).
MarkstepoptionalDurationMs, BonusMultiplier (a multiplier, not a fraction - 1.25 = +25%; <=1.0 is a no-op), MaxConsumes, RayDistance (default 20.0), DetonateDamage (final-consume burst; 0 disables), EffectAsset, Cause (inherited by the detonate burst), AtVictim (marks ctx.victim directly, no raycast).
BuffAurastepoptionalRadius, DurationMs, Multiplier, IncludeCaster (default true), EffectAsset, Slot (default "aura", stacks with the "primary" next-hit-buff slot).
BeamstepoptionalRange, IntervalMs, AmountPerTick, CostPerTick, TotalTicks (default 50), Radius (default 0.6), Cause, MaxMovementMeters (default 4.0; <=0 disables the channel-cancel check), VisualParticle, HitSound, OnHit[] (per-tick chain), Scaling.
VelocityImpulsestepoptionalForward, Up.
AggroSuppressstepoptionalRadius, DurationMs (present = timed suppress window; omitted = one-shot aggro drop).
VanishstepoptionalDurationMs.
KnockbackstepoptionalStrength, Up.
DeployablestepoptionalSpawnerId (resolved against the engine DeployableSpawner catalog), Offset ({X,Y,Z}), RotationMode, SpawnFace.
Stunstep (OnHit child only)optionalDurationMs, Stagger (true selects the lighter stagger variant).
StatDrainstep (OnHit child only)optionalStat (uppercase cost-stat id, e.g. MANA), Amount (drained from victim), ReturnPercent (0-1, fraction refunded to caster).
TauntstepoptionalRadius (default 12.0), DurationMs (default 5000). Needs a server's aggro integration wired up for a gameplay effect; cosmetic-only cast notify without one.
NativeChainstepoptionalInteraction (a RootInteraction id; blank = handler-time no-op), InteractionType (engine enum name, e.g. "Ability1", "Dodge"; blank resolves to Ability1), Cause (the damage cause the native chain's damage leaf(s) inherit - see below), SummaryArgs (cosmetic tooltip substitution, no gameplay effect).
SummonstepoptionalRoleId (NPC role to spawn; blank = no-op), DurationMs, Count (default 1), Offset ({X,Y,Z}).
ChangeStatstepoptionalStat (an EntityStat id), Amount (signed delta on the current value), Target (CASTER default | VICTIM). The generic sibling of StatDrain - a plain stat nudge, no cost-stat semantics.
* required

Worked step examples

A caster-centered AOE fire burst on hit, from the jar-shipped Fireball ability:

Fireball's Projectile.OnHit chain
{ "Type": "Projectile",
  "Projectile": {
    "Asset": "MMO_Fireball",
    "Impact": { "Sound": "SFX_Staff_Flame_Fireball_Impact", "Particles": "Explosion_Medium" },
    "OnHit": [ { "Type": "Damage", "Damage": { "Amount": 40.0, "Radius": 4.0, "Cause": "Fire" } } ],
    "RaycastMode": false, "MaxDistance": 20.0, "Count": 1, "IntervalMs": 0
  } }

A summon step, from the jar-shipped Summon Wolf ability:

Summon Wolf's Effects entry
{ "Type": "Summon",
  "Summon": { "RoleId": "MMO_Summoned_Wolf", "DurationMs": 30000, "Count": 1, "Offset": { "Y": 1.0 } } }

Scaling

An optional nested group on Damage/Dot/Zone/Beam's per-hit/per-tick Amount. Snapshot once at cast/apply time, never re-evaluated per tick. Two composable sub-groups fold additively into one factor:

text
amount *= (1 + statMax * PerStat.PerPoint + skillLevel * PerSkillLevel.PerLevel)
FieldTypeDefaultDescription
PerStatobject { Stat, PerPoint }optionalScales by a caster EntityStat's folded max value.
PerSkillLevelobject { Skill, PerLevel }optionalScales by the caster's level in a named skill. A null/blank Skill resolves to the ability's XpRouting first skill (the same way "$HELD" resolves).
* required
json
"Damage": { "Amount": 20, "Cause": "Physical",
  "Scaling": { "PerSkillLevel": { "Skill": "SWORDS", "PerLevel": 0.02 } } }

Presentation (shared type)

The same Presentation shape appears at two scopes: the ability's top-level field (the cast moment, plays once per successful cast, skipped entirely for a passive ability) and each EffectStep's own Presentation (the step's own moment). Teleport.Arrive and Projectile.Impact are typed sub-moments of the same shape.

FieldTypeDefaultDescription
SoundstringoptionalA SoundEvent id (vanilla or pack-authored).
ParticlesstringoptionalA particle-system id.
AnimationstringoptionalCast-animation cue (ability-level cast moment only).
AnimationItemstringoptionalItem-animation-set id, the third leg of the animation triple.
AnimationSlotstringoptionalHotbar slot the animation plays against.
CamerastringoptionalA camera-shake effect id. Cast-moment only.
FeedbackstringoptionalA FeedbackService moment id. Cast-moment only.
* required
A step-level Camera/Feedback is schema-legal but never plays - AbilityValidator warns if one is authored. Reserve those two fields for the ability's top-level Presentation.
json
"Presentation": { "Sound": "SFX_Staff_Flame_Fireball_Launch", "Particles": "Fire_Charge_Soak1" }

Parent inheritance for ability variants

A pack file can set a top-level Parent pointing at any other ability id (jar default, another pack's ability, or the owner overlay). Every top-level group leaf is inherited unless the child restates it - the smallest possible variant overrides one field:

Server/MMOSkillTree/Abilities/My_Fireball_Variant.json
{ "Parent": "fireball", "Cooldown": { "Ms": 5000 }, "Cost": { "Stat": "MANA", "Amount": 20 } }

This is confirmed by AbilityAssetCodecTest: a child overriding only Cooldown.Ms still inherits Cost, Effects, Presentation, and every other leaf from the parent. Remember the one exception - Effects is whole-array-only (see EffectStep).

Precedence and reload

defaults<pack<owner
  1. 1

    Jar defaults

    src/main/resources/Server/MMOSkillTree/Abilities/*.json - 91 files.
  2. 2

    Pack content

    Server/MMOSkillTree/Abilities/<id>.json in any installed pack. A matching id fully replaces the default unless it uses Parent.
  3. 3

    Owner overlay

    mods/mmoskilltree/ability-overrides.json folds last, per-leaf - the same overlay semantics as Parent. Shape: {"schemaVersion":1,"abilities":{"<id>":{...PascalCase AbilityAsset body...}}}.

ability-settings.json is the separate override config that gates the whole active-ability system on/off (AbilitySettingsConfig.enabled, the abilities feature-gate id) - it does not touch individual ability tuning, that is what ability-overrides.json is for.

/mmoability reload (admin) re-folds the catalog from live pack state plus a fresh disk re-read of ability-overrides.json. It does not force-rescan a brand-new zipped pack - that still needs a server restart.

abilities.json is retired (1.6.0 hard break)

The legacy JSON-blob override format at mods/mmoskilltree/abilities.json is never read again. Any file found there at startup is moved to mods/mmoskilltree/backup/ with a loud warning. Re-author overrides in ability-overrides.json instead.

Native-chain damage attribution

A NativeChain step's native re-caused Damage needs its own DamageCause leaf to attribute XP/masteries/rewards through the declaring ability instead of the held-weapon fallback. For jar-shipped abilities this is fully automatic (SchoolCauseGenerator pre-scans the jar defaults plus ability-overrides.json at startup and auto-registers MMO_Cause_<AbilityPascal> per ability).

Pack-authored NativeChain abilities need a manual DamageCause

A pack-authored NativeChain ability does not get the automatic treatment - author the DamageCause asset yourself:
Server/Entity/Damage/MMO_Cause_My_Native_Ability.json
{ "Parent": "PhysicalDamageCause", "Inherits": "PhysicalDamageCause" }

Then reference it: "NativeChain": { "Interaction": "...", "Cause": "MMO_Cause_My_Native_Ability" }. DamageCause assets decode before Interaction assets in Hytale's own load order, so no special pack ordering is needed.

paramKey resolution for modifiers

A mastery node or skill-tree reward that targets an ability (a FLAT/PERCENT/OVERRIDE/ADD_PARAM modifier keyed by a paramKey like damage or cooldownMs) only takes effect if that key resolves to a real field on the ability's authored step chain. AbilityValidator checks every authored paramKey against the engine's binding table and errors an unresolved one (surfaced by /mmoconfig validate) - an unresolved key would otherwise silently drop the modifier at cast time.

Minimal example

Server/MMOSkillTree/Abilities/My_Spark.json
{
  "Identity": { "NameKey": "ability.my_spark.name", "DescKey": "ability.my_spark.desc", "Icon": "Ingredient_Lightning_Essence" },
  "Cooldown": { "Ms": 6000 },
  "Cost": { "Stat": "MANA", "Amount": 15 },
  "XpRouting": { "Skills": ["MAGIC"] },
  "Effects": [ { "Type": "Damage", "Damage": { "Amount": 18, "Cause": "Lightning" } } ]
}

Full example (jar default, real)

src/main/resources/Server/MMOSkillTree/Abilities/Fireball.json
{
  "Name": "fireball",
  "Identity": { "NameKey": "ability.fireball.name", "DescKey": "ability.fireball.desc", "Icon": "Weapon_Staff_Crystal_Flame" },
  "Cooldown": { "Ms": 8000 },
  "Cost": { "Stat": "MANA", "Amount": 30 },
  "XpRouting": { "Skills": ["MAGIC"] },
  "Effects": [
    { "Type": "Projectile",
      "Projectile": {
        "Asset": "MMO_Fireball",
        "Impact": { "Sound": "SFX_Staff_Flame_Fireball_Impact", "Particles": "Explosion_Medium" },
        "OnHit": [ { "Type": "Damage", "Damage": { "Amount": 40.0, "Radius": 4.0, "Cause": "Fire" } } ],
        "RaycastMode": false, "MaxDistance": 20.0, "Count": 1, "IntervalMs": 0
      } }
  ],
  "Presentation": { "Sound": "SFX_Staff_Flame_Fireball_Launch", "Particles": "Fire_Charge_Soak1" }
}

Ability Mods Since 1.6.0

A grantable, player-facing improvement to an existing ability - a mastery-tree reward, a quest prize, or a shop offer that permanently buffs one specific ability rather than a whole skill.

Content pack assetServer/MMOSkillTree/AbilityMods/<id>.jsonAbilityMods/

AbilityModAsset.CODEC. Defaults < pack only - there is no owner-override layer for this type yet, unlike Abilities' three-layer fold.

FieldTypeDefaultDescription
TargetAbility*string-Must resolve in the ability catalog (validated non-null at decode; lowercased).
ModifiersarrayoptionalThe same modifier grammar mastery nodes use - see below. Parsed lazily and cached on first read; a malformed entry is skipped with a warning, not fatal to the whole asset.
NameKeystringoptionalDisplay name localization key.
DescKeystringoptionalDescription localization key.
NamestringoptionalEcho only.
* required

Modifier grammar (7 shapes)

Shared with mastery-node modifiers (AbilityModifierJson) - see also Masteries for the mastery-node side of the same grammar. shape is required, uppercase, defaults to FLAT if omitted.

FLAT

Adds a flat amount to the target field.

json
{ "shape": "FLAT", "key": "damage", "value": 4 }

PERCENT

Adds a percent multiplier to the target field.

json
{ "shape": "PERCENT", "key": "damage", "value": 0.15 }

OVERRIDE

Replaces the target field outright.

json
{ "shape": "OVERRIDE", "key": "cooldownMs", "value": 4000 }

ADD_PARAM

Adds a brand-new numeric param the base ability did not have.

json
{ "shape": "ADD_PARAM", "key": "armorPenetration", "value": 0.2 }

CONDITIONAL

Wraps another modifier so it only applies when a condition passes.

json
{ "shape": "CONDITIONAL",
  "condition": { "selfHpMax": 0.5, "hostileOnly": true },
  "apply": { "shape": "PERCENT", "key": "damage", "value": 0.25 } }

condition fields: selfHpMin, selfHpMax, targetHpMin, targetHpMax, hostileOnly, onCritOnly, filter. apply is a nested modifier object of any other shape.

TRIGGER

Fires an event-keyed effect rather than modifying a field.

json
{ "shape": "TRIGGER", "triggerEvent": "on_kill", "payload": { "healPercent": "0.1" } }

ADD_STEP

Splices a whole authored step into the target ability's chain - the same schema as an Effects entry.

json
{ "shape": "ADD_STEP", "insertAnchor": "ON_HIT", "sourceId": "council_arcane_focus",
  "stepPayload": { "Type": "ApplyEffect", "ApplyEffect": { "EffectAsset": "MMO_Slam_Slow", "DurationMs": 2000 } } }
FieldTypeDefaultDescription
insertAnchorstringoptionalSTART | END | ON_HIT. START/END prepend/append the top-level Effects[]; ON_HIT attaches into the target's first on-hit-attachable step (Projectile, Dash, or a non-splash Damage).
sourceIdstringoptionalAuthor dedupe key.
stepPayloadobjectoptionalA whole EffectStep body, decoded through EffectStep.CODEC directly.
* required

ON_HIT is rejected, not silently no-op'd, on an incompatible target

A Zone-only, Dot-only, splash-Damage-only, or native-chain-only target ability has no OnHit array to attach into. An ON_HIT anchor against one of those is rejected at content validation, so a bad ADD_STEP mod surfaces at /mmoconfig validate instead of silently doing nothing in-game.

Every shape also accepts optional scoping fields: targetAbility, targetSkill, combatTarget.

Granting

Grant through the unified Reward model's ABILITY_MOD kind:

json
{ "type": "ABILITY_MOD", "modId": "council_arcane_focus" }

from a quest reward, a dialogue Reward action, or a shop entry - the same one-shot pipeline every other reward kind uses, including the offline-retry queue. /mmoability grantmod and /mmoability revokemod (admin) do the same by console command for testing.

Real example

examples/council-quest-pack/Server/MMOSkillTree/AbilityMods/Council_Arcane_Focus.json
{
  "Name": "Council_Arcane_Focus",
  "TargetAbility": "arcane_missiles",
  "NameKey": "abilitymod.council_arcane_focus.name",
  "DescKey": "abilitymod.council_arcane_focus.desc",
  "Modifiers": [
    { "shape": "FLAT", "key": "damage", "value": 4 },
    { "shape": "ADD_STEP", "insertAnchor": "ON_HIT", "sourceId": "council_arcane_focus",
      "stepPayload": { "Type": "ApplyEffect", "ApplyEffect": { "EffectAsset": "MMO_Slam_Slow", "DurationMs": 2000 } } }
  ]
}