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.