Skip to content

New Ability Archetype Recipe

How to add a new authorable ability archetype (buff, aura, channel, beam, …) to the AbilityAuthoringWorkflow pipeline. The Leap archetype (Crash Landing) is the worked example — it was built by exactly this recipe.

Policy gate first (L7)

A new C++ base class exists only for a new VERB — a movement/spawn/targeting behavior the engine must physically perform (leap trajectory, beam trace, channel loop). Before writing any C++:

  • New parameters on an existing verb → CDO fields + a catalog row. No new class.
  • New combination of existing verbs → a Blueprint child or composed helpers. No new class.
  • Cost/cooldown/tags/cues/scaling are never a reason for a new base — that's the shared-GE + SetByCaller layer every player-activated base already carries.
  • Keep the hierarchy shallow: prefer extracted helpers (FAOEStatics pattern) over deep subclassing.

Also the GDD authoring rule (plan L6): the archetype must serve a committed Phase plan — nothing is authored around unbuilt features. Commit the plan (One-Pager + phase section) before code.

The recipe

1. Base class (only if the verb is new)

  • Subclass the branch that already carries the closest contract. Player-activated skills subclass USkillAbility/UAOESkillAbility so the shared SetByCaller cost GEs (Ge_ResonanceCost_Dynamic/Ge_StaminaCost_Dynamic), GE_Cooldown_Shared, and the FScalableFloat magnitude fields (L8) are inherited, not re-implemented. Procs subclass UOnHitAbility/UOnDamagedAbility for the trigger/chance/condition contract.
  • Add only the fields the first real ability uses (speculative fields get deleted, not wired). Tuned magnitudes are FScalableFloat; feel constants (arc height) stay plain floats.
  • Pin InstancingPolicy/NetExecutionPolicy in C++ if you deviate from the parent (the catalog contract spec bans NonInstanced fleet-wide).
  • Grant/spawn/damage paths stay authority-guarded; per-activation instance state must be reset in ActivateAbility (per-actor instances are reused).

2. Sweep every gate for the new shape

New data shapes must be taught to every validator/consumer that special-cases the parent (new-data-shape-sweeps-every-gate). Grep for the parent class name across:

  • Source/ProjectEternalEditor/Private/Validation/AbilityValidationCore.cppCheckMontageWiring is now catalog-driven: set the row's MontageRoute (below) instead of adding an IsA branch. Leap, for instance, is SkillMontages (fires its AOE at landing, so it validates like a plain skill — a warning, never the AOE-skill spawn-path error). The UEnemyAbility fork is the one exception still matched by an explicit class check (its subclasses bucket to the Enemy-family rows; the enemy-AOE fork data-drives through its own row since the EnemyAOE row landed). If a genuinely new montage behaviour appears, add an EMontageRoute value + a switch case here, then a route pin in AbilityValidation.spec.cpp.
  • Replace-style overrides on sibling classes (overrides-that-replace-drop-new-base-gates).
  • The archetype contract spec (ProjectEternal.Unit.Abilities.ArchetypeCatalog) — it picks the new row up automatically, but its checks only cover slots you DECLARE (step 4).

3. Headless spec

DEFINE_SPEC through the REAL exec path (see LeapAbility.spec.cpp / AOESkillAbility.spec.cpp): ticking CreateWorld rig, real shared GE assets loaded by path, costs deducted / cooldown expires / damage lands assertions, refusal paths leave state untouched, L8 curve scaling proven at level 2. Keep World->Tick steps below 0.4 s (MaxUndilatedFrameTime clamping starves GE timelines).

4. Catalog row (Config/DefaultGame.ini)

One +Archetypes=(...) row under [/Script/ProjectEternal.EternalAbilityArchetypeCatalog]: ArchetypeId, DisplayName, Category, CategoryColor (the Workshop tint — data, not a GetCategoryColor code edit), BaseClass, DefaultFolder, TemplateBP, bPlayerActivated, MontageRoute (step 2 — how the montage validator dispatches; omit for None), and the authoring contract — RequiredTagSlots / RequiredClassSlots / NumericSlots. Declare every slot the archetype's function depends on — an undeclared slot silently re-opens the "AOE-cost gap" class of bug the contract spec exists to catch. FindRowForClass is most-derived-wins, so a subclass row shadows its parent's row automatically.

5. Template asset BP

Author TPL_<ArchetypeId> in /Game/Gameplay/GameplayAbilities/Templates/ (folder is in DirectoriesToNeverCook). Duplicate the best existing exemplar of the archetype if one exists; author fresh only when the base has no BP children. Wire the full shared contract (cost GEs, GE_Cooldown_Shared, GE_Damage, sane default magnitudes) so the template is validator-green by construction — the wizard seeds every new ability from it.

6. Exemplar via the wizard/stamp

Author the first real ability from its One-Pager through Eternal.Ability.Stamp (or the Workshop wizard — same code path). Register its tags in Config/Tags/GameplayTags.ini (cooldown tag, asset tag; cue tags belong to the VFX hand-finish pass with their notifies). Validate: 0 errors. Montage, notifies, cues, and feel remain hand-work — the pipeline does plumbing only.

7. Prove it

Full test suite green (the contract spec now loads your template), validation sweep 0 errors, and a PIE grant through a kit/preset if the ability is player-facing.

Worked examples (Leap, EchoStrike)

Step Leap artifact EchoStrike artifact
Verb ULeapAbility — launch arc + LandedDelegate impact (Abilities/LeapAbility.h) UEchoStrikeAbility + AEchoStrikeActor — server-spawned replicated phantom repeats the owner's strike (Abilities/EchoStrikeAbility.h, Combat/EchoStrike/)
Gate sweep CheckMontageWiring leap route (SkillMontages) MontageRoute=None (proc); no-recursion guard native in ShouldProc
Spec ProjectEternal.Unit.Combat.LeapAbility (7 cases) ProjectEternal.Unit.Combat.EchoStrikeAbility (spawn/damage, proc-stream mark, no-chain)
Row ArchetypeId="Leap", NumericSlots include LeapDistance ArchetypeId="EchoStrike", RequiredClassSlots PhantomActorClass+PhantomDamageEffectClass
Template TPL_Leap (authored fresh — new base, no BP children) TPL_EchoStrike (authored fresh — new base)
Exemplar GA_CrashLanding from Design/Abilities/CrashLanding.ability.md GA_EchoStrike from Design/Abilities/EchoStrike.ability.md