Authoring Abilities & Ability Mods

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

Ability assets shipped
97
Active (hotbar-castable)
63
Passive / triggered
34
Damage schools
9
Content pack assetServer/MMOSkillTree/Abilities/<Skill>/<Name>.jsonAbilities/

One file per ability. The same schema reads the jar defaults, pack files, and the owner overlay at mods/mmoskilltree/ability-overrides.json. Ability improvements live at Server/MMOSkillTree/AbilityMods/<id>.json and have no owner-override layer - see Ability Mods below.

defaults<pack<owner

The filename is the ability id, lowercased when it is read: Arcane_Missiles.json becomes arcane_missiles. Subfolders are organizational and change nothing. The jar files its own defaults one folder per skill (Abilities/Magic/Fireball.json) purely so the roster reads well, so your pack file can sit in any folder, or none at all, and still override a jar default of the same name. Keep filenames unique across the whole folder tree: two files sharing a basename are two files claiming one id.

The asset is the runtime ability, not descriptive content beside it: what you author here is exactly what fires when a player presses the key. A pack file with the same id as a jar default replaces it entirely unless it uses Parent (see below).

The eight top-level groups

An ability file carries eight groups plus nothing else. There is no Targeting group and no separate list of effect steps: an ability's targeting and its whole effect sequence both live inside Body, composed the same way a weapon or a consumable's own interaction chain is composed.

FieldTypeDefaultDescription
IdentitygroupoptionalDisplay name key, description key, and icon.
CooldowngroupoptionalMilliseconds between casts.
CostgroupoptionalThe resource bar a successful cast charges.
XpRoutinggroupoptionalWhich skills the cast credits, and any held-weapon exclusions.
CastinggroupoptionalWhat the caster must clear, plus the client-clock declaration.
PresentationgroupoptionalThe cast moment: sound, particles, animation, camera, feedback.
PassiveTriggergroupoptionalWhich combat event fires the ability, and the filters that narrow it. Authoring this group is what makes an ability passive.
BodygroupoptionalThe whole cast chain. Everything the ability actually does.
* required

Every group, and every leaf inside every group, inherits independently: a Parent-linked child that overrides only Cooldown.Ms still inherits Cost, Body, Presentation, and every other leaf from its parent.

Identity

FieldTypeDefaultDescription
TitleKeystringoptionalDisplay name localization key.
DescriptionKeystringoptionalDescription localization key.
IconstringoptionalAn item id whose icon renders as the ability icon.
* required

An ability is passive because it carries a PassiveTrigger group

There is no active-or-passive flag to author. An ability with a PassiveTrigger group fires from the combat hook that group names instead of being cast on demand: it skips Cooldown, Cost and the Casting gates, and its top-level Presentation never plays. An ability without one is castable. The old Identity.Passive boolean still decodes so an older pack file loads, but it decides nothing and /mmoconfig validate warns about it: delete the leaf and let the group speak.

Cooldown

FieldTypeDefaultDescription
MslongoptionalMilliseconds between casts.
* required

Cost

FieldTypeDefaultDescription
StatstringoptionalResource-bar stat id charged on a successful cast, e.g. MANA, STAMINA.
AmountintoptionalAmount charged.
* 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.
ExcludeSkillsstring[]optionalSkill ids to exclude from held-weapon fallback routing.
* required

Casting

FieldTypeDefaultDescription
RequiresobjectoptionalThe one shared requirements block every quest, shop entry and board uses: factor bounds, a permission, prerequisites, and the AllOf / AnyOf / Not combinators.
SelfHpobject { Min, Max }optionalCaster health window, as 0.0-1.0 fractions: the cast is refused unless the caster's health fraction sits inside it. Author either bound alone, e.g. { "Max": 0.5 } for an execute-yourself-at-half-health ability.
HeldWeaponCategoriesstring[]optionalThe held weapon must resolve to any of these skill ids, e.g. ["BLUNT", "AXES"]. Array only: a single-category ability writes a one-element array. Omitted or empty means no weapon gate.
ClientClockboolfalseDeclares a body the caster's own client has to run - see Client clock.
* required
Three older cast predicates (a required armed slot, an invulnerability check, a minimum combo-hit count) have no live users across all 97 shipped abilities and carry no leaf here. Do not assume they are authorable in a pack file.

Presentation

A shared shape used at more than one place - see Presentation below for the full field list and both scopes it appears at.

PassiveTrigger

FieldTypeDefaultDescription
OnstringoptionalHitDealt | Kill | Finisher | DodgeArmed.
ChancedoubleoptionalLegal to author, but no shipped passive uses it - every one fires at 100%.
FilterstringoptionalRestricts which source ability triggers this passive; "any" = no restriction.
WeaponSkillsstring[]optionalOnly fires while wielding a weapon resolving to ANY of these skill ids; omitted or empty means no weapon gate.
SelfHpobject { Min, Max }optionalCaster health window, as 0.0-1.0 fractions. The same shape Casting.SelfHp uses.
TargetHpobject { Min, Max }optionalVictim health window, as 0.0-1.0 fractions: { "Max": 0.2 } is an execute that only fires on a target below one fifth health.
ComboFinisherOnlybooloptionalRestrict HitDealt to combo-finisher hits only.
Bonusobject { FromAbility, Multiplier, ScalesParam }optionalSource-ability bonus scaling: when the hit came from the ability named in FromAbility, the param named in ScalesParam (default armMultiplier) is scaled by Multiplier (default 1.0, no bonus).
* required

The two health windows and the bonus group are nested objects, not flat prefixed leaves:

json
"PassiveTrigger": {
  "On": "HitDealt",
  "WeaponSkills": ["SWORDS"],
  "TargetHp": { "Max": 0.35 },
  "Bonus": { "FromAbility": "whirlwind", "Multiplier": 1.5, "ScalesParam": "armMultiplier" }
}

Body: the ability's cast chain

Body is an ordinary Hytale root interaction. The mod's own node types compose freely with native Hytale nodes inside one chain, exactly the surface a weapon or a consumable's interaction chain uses, not a separate ability-only schema. A whole ability lives in one file:

json
"Body": {
  "Interactions": [
    { "Type": "MmoModSteps", "Anchor": "Start",
      "Next": { "...": "the ability's real first node" } }
  ],
  "RequireNewClick": false
}

RequireNewClick: false is what every shipped ability authors: the chain runs on the cast it was fired by rather than waiting for a fresh click.

Body also accepts a bare string naming a real chain file under Server/Item/RootInteractions/. That form is reserved for a genuinely shared fragment rather than a whole ability body.

Node types

Every node's "Type" is either a native Hytale type (Simple, ApplyForce, ApplyEffect, RunRootInteraction, a native damage leaf, and so on) or one of the mod's own. Any of them also accepts SummaryHidden: true, which hides that node from the ability's rendered description without changing what it does.

FieldTypeDefaultDescription
MmoCastAbilitynodeoptionalFires a real, fully gated ability cast from anywhere: a weapon proc, an NPC, a trap, a scroll, a block. See Casting from a consumable or a block.
MmoRequireGatenodeoptionalStops the chain unless the shared requirements block passes.
MmoGrantRewardnodeoptionalGrants a reward through the shared reward model, with an optional chance.
MmoSelectnodeoptionalPicks targets: a ray, a sphere, a cone, or the caster alone, with filters for allies, players and hostiles. Each target runs a fork chain; NoHit covers a total miss.
MmoModStepsnodeoptionalAn anchor a granted improvement can splice into. See Anchors.
MmoDamagenodeoptionalDamage, with optional splash radius, damage cause, crit and armor-penetration knobs, weapon-derived damage, stat and skill scaling, and knockback.
MmoDotnodeoptionalDamage over time: per-tick amount, interval, duration, stacking key and cap, plus the status effect it applies.
MmoZonenodeoptionalA ground zone that pulses damage or healing, with an optional falling-meteor or arrow-rain telegraph.
MmoBeamnodeoptionalA channeled beam: per-tick amount and cost, tick count, a movement cancel distance, and a repeating visual chain.
MmoProjectilenodeoptionalFires a projectile (or an instant raycast shot), with salvo count and interval, pierce, and an on-hit chain.
MmoHealnodeoptionalHeals, flat or as a percentage, instantly or over time.
MmoDashnodeoptionalA momentum dash with invulnerability frames, windup, optional movement-steered direction, a sweep radius and an on-hit chain.
MmoTeleportnodeoptionalBlinks the caster forward up to a maximum distance, with its own arrival moment.
MmoStunnodeoptionalStuns or staggers, with diminishing-returns knobs.
MmoMarknodeoptionalMarks a target so hits on it hit harder, with a detonation on the last consume.
MmoArmNextHitnodeoptionalArms the caster's next hit with a multiplier (armMultiplier) and an optional flat bonus (armFlatBonus) in one of four slots. Momentum: true selects the pure momentum arm, the dash or charge flavor: multiplier, duration and slot only. Left absent or false, the fuller next-hit buff runs instead, with its cast-time raycast, its flat bonus, its armed per-hit chain and its optional cast-time self-AOE.
MmoBuffAuranodeoptionalA radius buff around the caster for a duration.
MmoVanishnodeoptionalHides the caster for a duration.
MmoTauntnodeoptionalPulls nearby hostile attention onto the caster.
MmoAggroSuppressnodeoptionalDrops nearby hostile attention, one shot or for a window.
MmoHookshotPullnodeoptionalReels the caster toward a hit point at a capped speed.
MmoStatDrainnodeoptionalDrains a resource stat from the victim and optionally returns part of it.
MmoChangeStatnodeoptionalNudges a stat on the caster or the target, optionally for a duration.
MmoSummonnodeoptionalSummons temporary allies from an NPC role.
MmoDeployablenodeoptionalPlants a deployable construct such as a turret or a totem.
MmoApplyEffectnodeoptionalApplies a status effect whose duration scales with gear. A fixed-duration effect uses the plain native node instead.
* required

Prefer MmoSelect over a native selector for any targeting sweep leading into a number-bearing MMO node: it resolves on the server, which is what lets the same body fire identically from a hotbar slot, a scroll, a weapon proc, an NPC or a trap.

A sphere sweep that damages every hostile it finds, and shrugs off a total miss
{
  "Type": "MmoSelect",
  "Sphere": { "Radius": { "Base": 6.0, "ParamKeys": ["radius"] }, "MaxTargets": 8 },
  "Filter": { "HostileOnly": true },
  "HitEntity": [ { "Interactions": [ { "Type": "MmoDamage",
      "Amount": { "Base": 18.0, "ParamKeys": ["damage"] }, "Cause": "Arcane" } ],
    "RequireNewClick": false } ],
  "NoHit": { "Interactions": [ { "Type": "Simple", "RunTime": 0 } ], "RequireNewClick": false },
  "Next": "MMO_Frag_Anchor_End"
}

Shared fragments and naming

A node can reference a shared chain fragment by id instead of inlining it. Two ship with the jar, MMO_Frag_Anchor_End and MMO_Frag_Anchor_OnHit, both just anchor nodes. The rule of thumb: pull a fragment out to Server/Item/Interactions/<Name>.json and reference it by id only once three or more bodies would otherwise repeat the same nodes; one or two users stay inlined.

Name anything you author with your own prefix (MyPack_Ability_Frost_Nova) so it can never collide with another pack's fragment. The mod prefixes its own with MMO_ for the same reason.

Anchors: where a granted improvement attaches

A body should carry an anchor node with "Anchor": "Start" as its first node and one with "End" as its last, plus one with "OnHit" at the tail of any per-target or on-hit sub-chain you want to be spliceable. An ability improvement that adds a step inserts its own fragment at one of those three named points when the ability fires.

json
{ "Type": "MmoModSteps", "Anchor": "Start",
  "Next": { "...": "the ability's real first node" } }

Leaving the anchors out is not an error by itself (most abilities never receive a splice), but the content check warns about it, and it becomes an error the moment some shipped improvement actually targets that ability at an anchor the body does not have.

Numbers: Base, ParamKeys, Clamp

A number on an MMO node can be written two ways. A plain number is the shorthand for a fixed value nothing modifies:

json
"Amount": 12.0

The object form is what you write the moment the number should be modifiable or bounded:

json
"Amount": { "Base": 45.0, "ParamKeys": ["damage", "damageMultiplier"], "Clamp": { "Min": 0.0 } }
FieldTypeDefaultDescription
Basedouble0.0The authored number before anything modifies it.
ParamKeysstring[]optionalNames the keys a mastery node, a gear improvement or a reward can target to change this number at cast time, applied left to right. A leaf with no ParamKeys is simply not modifiable.
Clampobject { Min, Max }optionalOptional bounds applied after everything else. A project-wide bound for one key is authored separately under Server/MMOSkillTree/ModifierClamps/.
* required

Never author two keys for the same channel on one leaf

Writing ["distance", "range"] when both map to the same modifier source applies that modifier twice. One channel, one key.

The paramKey vocabulary

The vocabulary is a fixed list of 17 names, in three groups by where each one folds. A modifier naming anything else is reported by /mmoconfig validate and dropped, because a key nothing resolves would otherwise be silently ignored at cast time.

GroupKeysWhere it folds
Ability-level (2)cooldownMs, costPatched once per cast, before the body fires. Always available: no ParamKeys declaration needed.
Body params (11)damage, damageMultiplier, armMultiplier, armFlatBonus, damageRadius, radius, distance, durationMs, iframesDurationMs, pierceCount, onHitHealPercentFolded at fire time against whichever body leaf declares the key in its own ParamKeys. A key no leaf declares does nothing for that ability.
Not ability-scoped (4)lifesteal, comboFinisherBonus, lootMultiplier, schoolResistNever touch an ability at all: they are summed at their own seams, the weapon swing and the gathering luck roll. Always available.

armMultiplier and armFlatBonus are the MmoArmNextHit pair, deliberately kept out of damageMultiplier: a 1.5x arm scale and a +40 damage value must never share a fold bucket, or a FLAT of 2 would mean +2 damage on one and +2.0x on the other. lootMultiplier and schoolResist are PERCENT-only, and each pairs with a scope: TargetSkill for the loot roll, School for the resistance channel.

A pack can add a key of its own

Ship a Server/MMOSkillTree/ModifierClamps/<Key>.json asset and that key becomes known: one file both bounds the new key's fold and declares it, so an invented key can never ship unbounded and undeclared at once. The jar ships 19 clamp files: one per canonical key plus two reserved fold ids authoring "IsParamKey": false - HitDamage.json, which bounds the hit-side damage-percent sum, and DefenseReduction.json, which caps the victim's total percent damage reduction from defense. A reserved id is not a modifier key, so a modifier naming hitDamage or defenseReduction is flagged rather than quietly doing nothing. The owner tightens any of them in mods/mmoskilltree/ability-clamps.json.

Seven ParamKeys names are also reachable from gear, through the global ability channels: distance, radius, damageRadius, durationMs, iframesDurationMs, pierceCount, and cost. An item's Armor/Weapon/Utility block grants a flat additive bonus onto whichever of those a body declares, no AbilityMod required - see Gear stats: ability channels. distance is the only reachable name for range: the older range alias was dropped, and authoring both would double-apply the same gear bonus.

The trigger and body layering rule

A body may never wait on client-reported data

Nodes that wait on the player's own client (a charge-up hold, a first-click gate, a movement condition, a native selector) belong only in the trigger chain that fires the ability: the hotbar slot, the cast item, the weapon proc, the consumable. A server-fired body (an NPC cast, a trap, a chained ability) has no client to wait on, and keeping bodies free of those nodes is exactly what lets one body fire identically from every source. The content check reports a body containing one as an error.

The Guard Stance ability is the worked example: its body is the server half only (a status effect granting stagger immunity), while the hold-to-block mechanic, which genuinely needs a client-driven hold, lives outside the ability in its own chain, referenced from the weapon item. The consumable recipes below split the same way: the drink or read hold lives in the item's own chain, never inside a body.

Client clock

Some body nodes are run by the caster's client rather than read from it, a movement impulse being the usual case. Those are allowed in a body, and Casting.ClientClock: true declares them, so the cast is sent to the client to run instead of the server waiting for data nobody was asked to send and cancelling the cast when the wait times out.

json
"Casting": { "ClientClock": true }

You usually do not need it: the engine works the same thing out by itself for most chains. It misses exactly two shapes, a node tucked inside a wrapper node and a chain reached only through a reference to another chain, and that is what the declaration is for. Both directions are checked at boot and by /mmoconfig validate: a chain that waits on the client without the declaration is an error, and a declaration on an ability with nothing to wait for is an error too. Declare it, never default it.

Damage schools and Cause

A damage-bearing MMO node (MmoDamage, MmoDot, MmoZone, MmoBeam, MmoMark) carries an optional Cause naming a damage-cause id.9 of those ids are the mod's damage schools. Fire, Ice, Poison and Physical ride vanilla causes (Poison as an overlay that files it under Elemental beside Fire and Ice), while Lightning, Water, Arcane, Void and Life are MMO-authored, mirroring the essence items. Life is the drain, lifesteal and radiant school, an Elemental sub-cause with its own spring-green combat-text colour. Physical is the vanilla cause promoted to a full school: it is every plain weapon hit's school and the fallback for an untyped one, so no hit is ever school-less, and the vanilla sub-causes (Slashing, Bludgeoning, Projectile) fold into it. A vanilla cause outside that taxonomy is equally valid to name. An unset or unresolvable Cause falls back to Physical quietly at runtime, and /mmoconfig validate reports the unresolvable one. See the damage-school roster for the full list.

json
"Cause": "Ice"

A native damage node in a pack needs its own cause asset

A genuinely native damage leaf inside a body has no Cause field of its own; the mod bridges its own jar abilities by reading a school tag off the node, but that bridge never scans a pack. So either use one of the MMO damage nodes above, where the cause and the credit are automatic, or author your own damage-cause asset and point the native leaf at it.
Server/Entity/Damage/MMO_Cause_My_Native_Ability.json
{ "Parent": "PhysicalDamageCause", "Inherits": "PhysicalDamageCause" }

Damage-cause assets are read before interaction assets in Hytale's own load order, so referencing a new cause id from your own damage leaf works with no special ordering.

School resistance is owner config only

The school-resistance skill-tree reward targets these same 9 schools, but a pack cannot ship skill-tree nodes at all: resistance to a school is configured by the server owner in skill-tree.json.

Casting an ability from a consumable or a block

MmoCastAbility is the one node that fires an ability, so any native chain (a scroll, a potion, a weapon proc, an NPC, a trap, a pressure plate) can fire a real, fully gated cast. It names the ability exactly one way: Ability (a direct id), Slot (whatever the caster has bound to that slot), or Trigger (an input trigger).

FieldTypeDefaultDescription
GatesobjectoptionalRequireUnlock, RequireClass, PayCost, CheckConditions. All default on, so a scroll that should work for a player who never opened the tree turns them off one by one.
CooldownobjectoptionalUseAbilityPool decides whether this cast consults and stamps the ability's own cooldown; LocalMs is this node's own independent rate limit, in milliseconds.
ModifiersobjectoptionalFromPlayer, FromStack (the held item's own improvements), Inline.
ProgressionobjectoptionalFireObservers, AwardXp, Notify.
ChainobjectoptionalFailOnBlocked fails the chain on any refusal instead of quietly carrying on; GateOnly runs the whole gate ladder as a pure check that fires nothing.
* required

The two operator kill switches (the server-wide ability toggle and the per-world rules) always apply and cannot be opted out of.

A scroll casts and then consumes: the cast node sets Chain.FailOnBlocked, so a refused cast skips the consume for free and a blocked scroll read never eats the item.

Cast, then consume, only if the cast was allowed
{
  "Type": "MmoCastAbility",
  "Ability": "fireball",
  "Gates": { "RequireUnlock": false, "RequireClass": false, "PayCost": false, "CheckConditions": true },
  "Cooldown": { "UseAbilityPool": false },
  "Progression": { "AwardXp": false },
  "Chain": { "FailOnBlocked": true },
  "Next": { "Type": "ModifyInventory", "AdjustHeldItemQuantity": -1 }
}

A potion is the other way round, because a drink hold can be interrupted and refunded: run a GateOnly node first, so a doomed cast never starts the hold, put the drink hold next, and hang the real cast (same ability, same gates, without GateOnly) off the consume's success branch. An interrupted drink then refunds cleanly, and a completed drink can never produce a free cast.

Presentation (shared shape)

The same Presentation shape appears at two scopes: the ability's own top-level group (the cast moment, played once per successful cast, skipped entirely for a passive) and the per-node moments on the nodes that have one (MmoZone's Impact and Tick, MmoDot.Tick, MmoBeam.Hit, MmoDash.Travel, MmoProjectile.Impact, MmoTeleport.Arrive).

FieldTypeDefaultDescription
SoundstringoptionalA sound event id, vanilla or pack-authored.
Particlesstring or object { Id, MaxMs }optionalThe particle system for this moment. A bare string authors Id alone, which is the usual form. MaxMs caps a world-position one-shot spawn's playback in milliseconds; omit it for the shared four-second default.
Animationstring or object { Name, Item, Slot }optionalThe animation cue (cast moment only). A bare string authors Name alone, the clip resolving against the caster's held-item set. Item names the animation set the clip resolves in instead, and Slot the hotbar slot it plays against.
CamerastringoptionalA camera-shake id. Cast moment only.
FeedbackstringoptionalA feedback moment id. Cast moment only.
EffectstringoptionalA cosmetic status effect that dresses the moment, such as a dash trail. A gameplay status effect is not this leaf: that stays on the node that owns the mechanic.
* required

Particles and Animation are nested groups with a string shorthand, so the common case stays a single word and the fuller form is there when you need it:

json
"Presentation": { "Sound": "SFX_Staff_Flame_Fireball_Launch", "Particles": "Fire_Charge_Soak1" }

"Presentation": {
  "Particles": { "Id": "Fire_Charge_Soak1", "MaxMs": 1500 },
  "Animation": { "Name": "CastOverhead", "Item": "Weapon_Staff_Crystal_Flame" }
}

MaxMs only reaches a spawn planted at a bare world position, the one flavor nothing owns a lifecycle for. A model-attached emitter or a particle carried by a status effect is bounded by whatever it rides and ignores the leaf.

Each site reads only part of that list, because a zone tick has no animation to play and a dash trail is not a particle. /mmoconfig validate warns about a leaf authored where it is never read, and about a cast-moment group on a passive, which never casts.

Parent inheritance for ability variants

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

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

Precedence and reload

defaults<pack<owner
  1. 1

    Jar defaults

    Server/MMOSkillTree/Abilities/<Skill>/<Name>.json - 97 files, filed by skill.
  2. 2

    Pack content

    The same path in any installed pack, in any folder you like. A matching id fully replaces the default unless it uses Parent.
  3. 3

    Owner overlay

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

The clamp assets fold the same three ways, on their own file. A pack ships Server/MMOSkillTree/ModifierClamps/<Key>.json to bound (or declare) a key, and the owner has the last word in mods/mmoskilltree/ability-clamps.json, one entry per paramKey merged leaf by leaf over defaults and packs. That is where a server tightens a runaway stacking cap without touching either the ability or the pack that introduced the key.

An Effects key in the owner overlay is refused at load

Overriding an ability with an Effects key fails to load with a message pointing at Body. Re-author the override as a Body, or, for a small numeric tweak, target the referenced chain file instead of overriding the ability.

ability-settings.json is the separate owner file that switches the whole active-ability system on and off. 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 read of ability-overrides.json. It does not force a rescan of a brand-new zipped pack, which still needs a server restart - see Building the zip for how that zip is made.

abilities.json is retired (1.6.0 hard break)

The old 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.

Retired body keys

These flat leaves were removed, not deprecated. A body still carrying one loads cleanly and simply stops playing that moment, so the server names every stale key it finds in a pack file or the owner overlay at boot, one warning per file.

Removed keyAuthor instead
MmoZone.ImpactSound / ImpactParticleMmoZone.Impact.Sound / Impact.Particles
MmoZone.TickSound / TickParticleMmoZone.Tick.Sound / Tick.Particles
MmoDot.TickSoundMmoDot.Tick.Sound
MmoBeam.HitSoundMmoBeam.Hit.Sound
MmoDash.TrailEffectMmoDash.Travel.Effect
MmoZone.MeteorProjectileAsset / MeteorDropHeightMmoZone.Meteor.ProjectileAsset / Meteor.DropHeight
MmoZone.VolleyArrowProjectileAsset and friendsMmoZone.Volley.ProjectileAsset / Volley.ArrowsPerTick / Volley.SkyHeight
MmoDash.DirectionModeMmoDash.Direction.Movement (present = steer by movement), or omit Direction for look direction

Minimal example

The smallest useful pack file is a variant of something that already exists:

Server/MMOSkillTree/Abilities/My_Quick_Fireball.json
{
  "Parent": "fireball",
  "Cooldown": { "Ms": 5000 }
}

Full example (a real jar ability)

Server/MMOSkillTree/Abilities/Archery/Sentry_Turret.json
{
  "Identity": {
    "TitleKey": "ability.sentry_turret.name",
    "DescriptionKey": "ability.sentry_turret.desc",
    "Icon": "Weapon_Deployable_Turret"
  },
  "Cooldown": { "Ms": 30000 },
  "Cost": { "Stat": "STAMINA", "Amount": 20 },
  "XpRouting": { "Skills": ["ARCHERY"] },
  "Casting": { "HeldWeaponCategories": ["ARCHERY"] },
  "Presentation": {
    "Sound": "SFX_Bow_T2_Shoot",
    "Particles": "Bow_Signature_Launch"
  },
  "Body": {
    "Interactions": [
      {
        "Type": "MmoDeployable",
        "SpawnerId": "MMO_Sentry_Turret",
        "SpawnFace": "UP",
        "Turret": true
      }
    ],
    "RequireNewClick": false
  }
}

Ability Mods Since 1.6.0

A grantable, player-facing improvement to an ability the player already has: a mastery reward, a quest prize, a shop offer, or something a piece of gear grants while it is equipped.

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

The filename is the id. Defaults and packs only: there is no owner-override layer for this type yet, unlike an ability's three-layer fold.

FieldTypeDefaultDescription
TargetAbilitystringoptionalOne ability id this improvement applies to.
TargetSkillstringoptionalOr, instead, every ability that routes XP to this skill. Author one of the two: an asset with neither is refused.
TitleKeystringoptionalDisplay name localization key.
DescriptionKeystringoptionalDescription localization key.
Displayobject { ValueFrom, Format }optionalOptional tooltip hint: which number to show, and whether to show it as a percent or a flat value.
ModifiersarrayoptionalThe same modifier grammar mastery nodes use - see below. A malformed entry is skipped with a warning rather than failing the whole file.
* required

Modifier grammar (5 shapes plus a Condition gate)

Shared with mastery-node modifiers - see Masteries for the mastery side of the same grammar. The grammar is PascalCase (Shape / ParamKey / Value); Shape is uppercase and defaults to FLAT when omitted. A field name's casing is forgiven, so paramKey still lands on ParamKey, but each field has exactly one name and there is no Key alias. Two older shape words still parse: OVERRIDE and ADD_PARAM both read as SET, since all three mean replace-the-value.

The CONDITIONAL wrapper and the flat HP bounds are gone

A modifier written as a CONDITIONAL wrapper around an inner modifier no longer parses, and neither do the flat lower-case HP bounds a condition used to carry. Write the condition directly on the modifier it gates, with the nested SelfHp / TargetHp groups shown below. Re-author both before installing an older pack against 1.6.0: nothing rewrites them for you.

FLAT

Adds a flat amount to the target number.

json
{ "Shape": "FLAT", "ParamKey": "damage", "Value": 4 }

PERCENT

Adds a percent multiplier to the target number.

json
{ "Shape": "PERCENT", "ParamKey": "damage", "Value": 0.15 }

SET

Replaces the target number outright (last-write-wins), or writes a brand-new number the base ability did not author.

json
{ "Shape": "SET", "ParamKey": "cooldownMs", "Value": 4000 }

The Condition gate

Any modifier can carry a Condition so it only applies when the state passes:

json
{ "Shape": "PERCENT", "ParamKey": "damage", "Value": 0.25,
  "Condition": { "SelfHp": { "Max": 0.5 }, "HostileOnly": true } }

Condition fields: nested SelfHp / TargetHp groups (each { Min, Max } as 0-1 fractions), HostileOnly, OnCritOnly, Filter.

TRIGGER

Subscribes the player to a passive ability rather than modifying a number. TargetAbility names a passive that already exists in the catalogue, with its own PassiveTrigger group carrying the event, the filters and the HP gates; granting the improvement is what switches that passive on for the player. A TRIGGER modifier carries no ParamKey.

Server/MMOSkillTree/AbilityMods/Mmo_Boon_Duelists_Instinct.json (the Modifiers entry)
{ "Shape": "TRIGGER", "TargetAbility": "def_passive_punishing_dodge" }

ADD_STEP

Splices an authored interaction fragment into the target ability's Body at one of its anchors. StepPayload is the id of a real fragment file you ship under Server/Item/Interactions/, never an inline block of nodes.

json
{ "Shape": "ADD_STEP", "InsertAnchor": "ON_HIT", "SourceId": "council_arcane_focus",
  "StepPayload": "MMO_Frag_ArcaneFocusProc" }
FieldTypeDefaultDescription
InsertAnchorstringoptionalSTART | END | ON_HIT, matching the Start / End / OnHit anchor nodes in the target body.
StepPayloadstringoptionalThe id of the interaction fragment to splice in.
SourceIdstringoptionalA dedupe key, so the same splice granted twice still lands once. Recommended, and not allowed on a repeatable mastery node.
* required

A missing anchor is an error, not a silent no-op

A splice naming an anchor the target body does not have is reported at /mmoconfig validate, as is a missing payload, a missing anchor name, or a target ability that does not exist. Author the three anchor nodes in any ability you expect packs to extend.

Every shape also accepts optional scoping fields: TargetAbility, TargetSkill, School, CombatTarget.

Granting an improvement

Four routes, all content, no code:

  • A reward. The shared reward model's Mmo_Ability_Mod kind, from a quest, a dialogue option, a mastery node or a shop entry, through the same one-shot pipeline every other reward kind uses, offline retry included.
  • A command. /mmoability grantmod and /mmoability revokemod (admin), for testing or a manual grant.
  • Gear. An ItemAbilityGrants file grants improvements while a matching item is held, worn or in the offhand.
  • A specific item stack. A stack can carry its own rolled improvements as item metadata, which is how enhanced loot works.
json
{ "Kind": "Mmo_Ability_Mod", "Params": { "Mod": "council_arcane_focus" } }

Gear grants

Server/MMOSkillTree/ItemAbilityGrants/Mmo_Grant_CrystalFlameStaff.json
{
  "Items": ["Weapon_Staff_Crystal_Flame"],
  "Apply": { "WhenHeld": true },
  "Grants": ["mmo_gear_ember_focus", "mmo_gear_arcane_efficiency"]
}
FieldTypeDefaultDescription
Itemsstring[]optionalExact item ids to match.
ItemTagsstring[]optionalOr match by the item's own tag group keys, so your pack can tag its own armor or trinkets to opt in.
ApplyobjectoptionalThree independent switches: WhenHeld (default on), WhenWornArmor, WhenOffhand.
Grantsstring[]optionalThe improvements to grant while the item qualifies, by id.
* required

Per-stack rolled improvements

A single item stack can carry its own improvements in its metadata, each a reference to an existing improvement plus a scale multiplier. The scale multiplies flat and percent modifiers only, never an override, since scaling an override would quietly rewrite authored balance.

json
{ "MmoAbilityMods": { "V": 1, "Mods": [ { "Mod": "mmo_gear_vortex_amplifier", "Scale": 1.0 } ] } }

Two content routes hand a player pre-stamped gear, both taking that same metadata block directly:

A drop list entry, for ground-spawned loot
{ "ItemId": "Utility_Leather_Quiver", "QuantityMin": 1, "QuantityMax": 1,
  "Metadata": { "MmoAbilityMods": { "V": 1, "Mods": [
      { "Mod": "mmo_gear_vortex_amplifier", "Scale": 1.0 } ] } } }
A chain node granting the stack directly (a quest, bounty or shop delivery)
{ "Type": "ModifyInventory", "ItemToAdd": { "Id": "Utility_Leather_Quiver", "Quantity": 1,
    "Metadata": { "MmoAbilityMods": { "V": 1, "Mods": [
        { "Mod": "mmo_gear_vortex_amplifier", "Scale": 1.0 } ] } } } }

Real example

Server/MMOSkillTree/AbilityMods/Council_Arcane_Focus.json
{
  "TargetAbility": "arcane_missiles",
  "TitleKey": "abilitymod.council_arcane_focus.name",
  "DescriptionKey": "abilitymod.council_arcane_focus.desc",
  "Display": { "ValueFrom": "damage", "Format": "percent" },
  "Modifiers": [
    { "Shape": "PERCENT", "ParamKey": "damage", "Value": 0.1 },
    { "Shape": "ADD_STEP", "InsertAnchor": "ON_HIT", "SourceId": "council_arcane_focus",
      "StepPayload": "MMO_Frag_ArcaneFocusProc" }
  ]
}