Tutorial: Flame Cleave on Ember Axe

This tutorial adapts the actual 0.5.9 AbilityContentExample and DemoItemContent public calls. Run it from a registered owner's GameDataReady callback.

1. Register and activate the ability

using System;
using FTK2.ModAPI.Content;
using FTK2.ModAPI.Content.Abilities;
using FTK2.ModAPI.Content.Items;
using FTK2.ModAPI.Core;

public sealed class FlameCleaveContent
{
    private ICustomAbilityActivation? abilityActivation;
    private ICustomItemActivation? weaponActivation;

    public void Register(IModContext mod)
    {
        var abilityId = ContentId.Parse(mod.Metadata.Id + ":flame_cleave");
        var abilityName = ContentId.Parse(mod.Metadata.Id + ":abilities.flame_cleave.name");
        var abilityDescription = ContentId.Parse(mod.Metadata.Id + ":abilities.flame_cleave.description");

        Require(mod.Localization.TryRegisterEnglish(
            abilityName, "Flame Cleave", out var error), error);
        Require(mod.Localization.TryRegisterEnglish(
            abilityDescription,
            "A sweeping axe strike that can set its target ablaze.",
            out error), error);

        Require(mod.Abilities.TryCreateFromTemplate(
            abilityId, "AXE_BLEED_ATTACK", out var abilityDraft, out error), error);

        var ability = abilityDraft!
            .WithGameplay(new AbilityGameplayOverrides(
                targetArea: AbilityTargetArea.Single,
                target: AbilityTarget.Enemy,
                animation: AbilityAnimation.Slash,
                effects: new AbilityEffect[] {
                    new AbilityStatChange(
                        ItemStat.HP,
                        AbilityDamageType.Physical,
                        isBlockable: true),
                    new AbilityAddStatus("STATUS_FIRE_00"),
                },
                tags: new[] { "ATTACK", "MELEE", "MODAPI" }))
            .WithLocalization(abilityName, abilityDescription);

        Require(mod.Abilities.TryRegister(ability, out error), error);

        var abilityResult = mod.Abilities.Activate(abilityId, out abilityActivation);
        Require(
            (abilityResult == CustomAbilityActivationResult.Activated ||
             abilityResult == CustomAbilityActivationResult.AlreadyActive) &&
            abilityActivation != null && abilityActivation.IsActive,
            "Ability activation failed: " + abilityResult + "; " + abilityActivation?.Detail);

        RegisterWeapon(mod, abilityId);
    }

    private void RegisterWeapon(IModContext mod, ContentId abilityId)
    {
        var weaponId = ContentId.Parse(mod.Metadata.Id + ":ember_axe");
        var weaponName = ContentId.Parse(mod.Metadata.Id + ":items.ember_axe.name");
        var weaponDescription = ContentId.Parse(mod.Metadata.Id + ":items.ember_axe.description");

        Require(mod.Localization.TryRegisterEnglish(
            weaponName, "Ember Axe", out var error), error);
        Require(mod.Localization.TryRegisterEnglish(
            weaponDescription,
            "A fierce axe with tuned damage and native axe attacks.",
            out error), error);

        Require(mod.CustomItems.TryCreateFromTemplate(
            weaponId, "AXE_BANDIT_LIGHT_01", out var weaponDraft, out error), error);

        var weaponGameplay = new ItemGameplayOverrides(
            minTier: 2,
            maxTier: 3,
            rarity: ItemRarity.Rare,
            stackable: false,
            ammo: 0,
            useContext: ItemUseContext.None,
            equipment: new ItemEquipmentOverride(
                new[] { ItemEquipmentSlot.MainHand },
                new[] {
                    new ItemStatModifier(ItemStat.ATK, 18),
                    new ItemStatModifier(ItemStat.CRT, 12),
                    new ItemStatModifier(ItemStat.EVD, 5),
                },
                new[] { "STATUS_IMMUNITY_FIRE" },
                maxCharges: -1),
            interaction: new ItemInteractionOverride(
                new[] {
                    new ItemAbilityReference(
                        abilityId.ToString(),
                        new ItemSkillRoll(.5m, 1m, 0, ItemStat.STR, 3)),
                    new ItemAbilityReference(
                        "AXE_HEAVY_ATTACK",
                        new ItemSkillRoll(0m, 1.5m, -20, ItemStat.STR, 4)),
                },
                new[] { abilityId.ToString(), abilityId.ToString(), "AXE_HEAVY_ATTACK" },
                shuffleAbilityBag: true));

        var weapon = weaponDraft!
            .WithValue(210)
            .WithGameplay(weaponGameplay)
            .WithLocalization(weaponName, weaponDescription);

        Require(mod.CustomItems.TryRegister(weapon, out error), error);

        var itemResult = mod.CustomItems.Activate(weaponId, out weaponActivation);
        Require(
            (itemResult == CustomItemActivationResult.Activated ||
             itemResult == CustomItemActivationResult.AlreadyActive) &&
            weaponActivation != null && weaponActivation.IsActive,
            "Item activation failed: " + itemResult + "; " + weaponActivation?.Detail);
    }

    public void Deactivate()
    {
        weaponActivation?.Deactivate(); // dependent item first
        abilityActivation?.Deactivate();
    }

    private static void Require(bool passed, string? error)
    {
        if (!passed) throw new InvalidOperationException(error ?? "Content registration failed.");
    }
}

2. Understand the dependency order

The custom ability must be active before Ember Axe activation. Otherwise item activation reports CUSTOM_ABILITY_DEPENDENCY_NOT_ACTIVE. During full cleanup, release live runtime items, deactivate/remove Ember Axe, then deactivate/remove Flame Cleave and localization.

3. Understand presentation reuse

Flame Cleave keeps its own namespaced gameplay and localization identity. Its activation reuses the AXE_BLEED_ATTACK native presentation record, icon, and compatible asset references. It does not create a new animation, model, VFX, SFX, or status definition.

Common errors

Error Cause
INVALID_CONTENT_OWNER The definition or localization namespace does not match the registered mod owner.
TemplateMissing The selected native template does not exist in the active game-data generation.
UNKNOWN_EXISTING_STATUS An AbilityAddStatus key is absent.
CUSTOM_ABILITY_DEPENDENCY_NOT_ACTIVE The item was activated before its custom ability.
CannotDeactivateWhileReferenced A dependent item still grants the ability.
MAIN_THREAD_REQUIRED A runtime service was called from a worker thread.

See CustomAbilityDefinition, AbilityGameplayOverrides, ItemInteractionOverride, and CustomAbilityActivationResult.