Gear

Register custom weapons, armor sets, hybrid subclasses, trim patterns, trim materials, enchantments, and equipment scanners.

Weapons

Any Item can be registered as a Craftics weapon. The registration gives it a damage type, base stats, and an optional on-hit ability. Call CrafticsAPI.registerWeapon(item, entry) during onCrafticsInit().

API Method

CrafticsAPI.registerWeapon(Item item, WeaponEntry entry);

WeaponEntry Builder

WeaponEntry entry = WeaponEntry.builder(myItem)
    .damageType(DamageType.SLASHING)   // damage category
    .attackPower(8)                    // flat attack value
    .apCost(1)                         // action points per attack
    .range(1)                          // attack range in tiles
    .ranged(false)                     // true for projectile weapons
    .breakChance(0.0)                  // probability of breaking on use (0.0 to 1.0)
    .ability(myAbilityHandler)         // optional on-hit ability
    .build();
Builder Method Type Default Description
damageType(DamageType)enumPHYSICALWeapon's damage category
attackPower(int)int1Base attack power
attackPower(IntSupplier)supplierDynamic attack power computed each time the weapon attacks
apCost(int)int1Action point cost per attack
range(int)int1Attack range in tiles
ranged(boolean)boolfalseWhether this is a projectile weapon
breakChance(double)double0.0Chance to break on use (0.0 to 1.0)
ability(WeaponAbilityHandler)handlernullOn-hit ability (see below)

DamageType Values

Every weapon belongs to one of eight damage types. Armor sets, trims, and player affinity points grant bonuses to specific types.

ValueDisplay NameTypical Weapons
SLASHINGSlashingSwords
CLEAVINGCleavingAxes
BLUNTBluntMaces, shovels
WATERWaterTridents, corals
SPECIALSpecialBlaze rods, end rods, breeze rods
PETPetPet and ally attacks
RANGEDRangedBows, crossbows
PHYSICALPhysicalDefault / untyped weapons

Built-in Ability Factories (Abilities class)

The Abilities class provides static factory methods that return WeaponAbilityHandler instances. Compose multiple abilities with .and().

WeaponAbilityHandler handler = Abilities.bleed()
    .and(Abilities.sweepAdjacent(0.10, 0.05));
Factory MethodDescription
bleed()Applies bleed stacks equal to the weapon's Sharpness enchant level.
sweepAdjacent(baseChance, bonusPerPoint)Chance to hit one adjacent enemy for half damage. Scales with SLASHING affinity. Chance = baseChance + (affinity * bonusPerPoint) + (luck * 0.02).
armorIgnore(baseChance, bonusPerPoint)Chance to permanently destroy a portion of the target's defense and deal that amount as bonus damage. Scales with CLEAVING affinity.
stun(baseChance, bonusPerPoint)Chance to stun the target for one turn. Scales with BLUNT affinity.
knockbackDirection(distance)Pushes the target away from the player by up to N tiles, checking bounds and walkability each step.
aoe(radius, damageMultiplier)Hits all non-ally enemies within radius (Manhattan distance) of the target for (int)(baseDamage * damageMultiplier).
applyEffect(type, turns, amplifier)Applies a status effect to the target. Supported types: POISON, BURNING, SOAKED, SLOWNESS, CONFUSION.
pierce()Hits the first enemy directly behind the target in the attack direction for full base damage.
fireDamage(bonusDmg)Sets the target on fire and deals flat bonus fire damage.

Custom Ability Handler

WeaponAbilityHandler is a @FunctionalInterface. Implement it directly for fully custom on-hit logic.

@FunctionalInterface
public interface WeaponAbilityHandler {
    WeaponAbility.AttackResult apply(
        ServerPlayerEntity player,
        CombatEntity target,
        GridArena arena,
        int baseDamage,
        PlayerProgression.PlayerStats stats,
        int luckPoints
    );

    // Chain with another handler:
    default WeaponAbilityHandler and(WeaponAbilityHandler next);
}

Return a WeaponAbility.AttackResult(totalDamage, messages, extraTargets). The messages list holds combat log strings and extraTargets lists any additional entities your ability hit.

JSON Datapack Schema

Place weapon JSON files at data/<namespace>/craftics/weapons/*.json. The ability array is an ordered list of built-in ability blocks chained together.

FieldTypeRequiredDescription
itemstringyesFull item registry ID, e.g. minecraft:diamond_sword
damage_typestringnoDamageType name (case-insensitive). Defaults to PHYSICAL.
attack_powerintnoBase attack value. Defaults to 1.
ap_costintnoAction point cost per attack. Defaults to 1.
rangeintnoAttack range in tiles. Defaults to 1.
rangedboolnoWhether this is a projectile weapon. Defaults to false.
break_chancefloatnoProbability of breaking on use (0.0 to 1.0). Defaults to 0.0.
abilityarraynoOrdered list of ability objects (see ability kinds below).

Ability object kind values and their fields:

kindExtra Fields
bleed(none)
pierce(none)
sweepbase_chance (float, default 0.10), bonus_per_point (float, default 0.05)
armor_ignorebase_chance (float, default 0.10), bonus_per_point (float, default 0.05)
stunbase_chance (float, default 0.10), bonus_per_point (float, default 0.05)
knockbackdistance (int, default 1)
aoeradius (int, default 1), damage_multiplier (float, default 0.5)
fire_damagebonus_damage (int, default 2)
apply_effecteffect (string, e.g. POISON), turns (int, default 3), amplifier (int, default 0)
{
  "item": "minecraft:diamond_sword",
  "damage_type": "slashing",
  "attack_power": 7,
  "ap_cost": 1,
  "range": 1,
  "ranged": false,
  "break_chance": 0.0,
  "ability": [
    { "kind": "bleed" },
    { "kind": "sweep", "base_chance": 0.1, "bonus_per_point": 0.05 }
  ]
}

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.Abilities;
import com.crackedgames.craftics.api.registry.WeaponEntry;
import com.crackedgames.craftics.combat.DamageType;

// Heavy Claymore: cleaving type, armor ignore + knockback
Item claymore = Registries.ITEM.get(Identifier.of("mymod", "heavy_claymore"));
CrafticsAPI.registerWeapon(claymore, WeaponEntry.builder(claymore)
    .damageType(DamageType.CLEAVING)
    .attackPower(7)
    .apCost(2)
    .range(1)
    .ability(Abilities.armorIgnore(0.15, 0.03)
        .and(Abilities.knockbackDirection(2)))
    .build());

Armor Sets

Armor sets grant per-piece damage affinity bonuses and full-set stat bonuses when all four matching armor pieces are worn. The set ID must match the material key that PlayerCombatStats.getArmorSet() derives from the player's equipped armor. Call CrafticsAPI.registerArmorSet(entry) during onCrafticsInit().

Damage affinity is per-piece: each worn piece of a material contributes half the damageBonus value for that type. A full four-piece set contributes twice the value. The flat stat bonuses (speedBonus, defenseBonus, etc.) apply only when all four pieces are worn.

API Method

CrafticsAPI.registerArmorSet(ArmorSetEntry entry);

ArmorSetEntry Builder

ArmorSetEntry entry = ArmorSetEntry.builder("mymod:mythril")
    .damageBonus(DamageType.SLASHING, 2)  // affinity value per 2 pieces (per-piece = 1)
    .damageBonus(DamageType.SPECIAL, 1)
    .allDamageBonus(1)                    // applied first; per-type entries override it
    .speedBonus(1)
    .apBonus(0)
    .defenseBonus(3)
    .attackBonus(1)
    .apCostReduction(0)
    .description("Mythril Armor: light and sharp")
    .build();

CrafticsAPI.registerArmorSet(entry);
Builder MethodTypeDefaultDescription
damageBonus(DamageType, int)per-type0Affinity value per 2 pieces for this damage type. A single piece grants half this amount.
allDamageBonus(int)all typesSets the same affinity value for every damage type. Per-type entries override it for that specific type.
speedBonus(int)int0Movement speed bonus (full set only)
apBonus(int)int0Extra action points per turn (full set only)
defenseBonus(int)int0Defense stat bonus (full set only)
attackBonus(int)int0Base attack bonus (full set only)
apCostReduction(int)int0Reduction in AP cost per attack (full set only)
description(String)string""Tooltip description shown in the combat HUD

JSON Datapack Schema

Place armor set JSON files at data/<namespace>/craftics/armor_sets/*.json. The id must match the armor-set name your material produces.

FieldTypeRequiredDescription
idstringyesArmor-set material key, e.g. mythril
descriptionstringnoTooltip description
all_damage_bonusintnoAffinity value applied to every damage type first
damage_bonusesarraynoPer-type affinity overrides: each object has type (DamageType name) and amount (int)
speed_bonusintnoSpeed bonus (full set only)
ap_bonusintnoExtra action points (full set only)
defense_bonusintnoDefense bonus (full set only)
attack_bonusintnoAttack bonus (full set only)
ap_cost_reductionintnoAP cost reduction per attack (full set only)
{
  "id": "mythril",
  "description": "Mythril Set: light and sharp",
  "all_damage_bonus": 0,
  "damage_bonuses": [
    { "type": "SLASHING", "amount": 2 },
    { "type": "SPECIAL",  "amount": 1 }
  ],
  "speed_bonus": 1,
  "ap_bonus": 0,
  "defense_bonus": 3,
  "attack_bonus": 1,
  "ap_cost_reduction": 0
}

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.registry.ArmorSetEntry;
import com.crackedgames.craftics.combat.DamageType;

CrafticsAPI.registerArmorSet(ArmorSetEntry.builder("mythril")
    .damageBonus(DamageType.SLASHING, 2)
    .damageBonus(DamageType.SPECIAL, 1)
    .speedBonus(1)
    .defenseBonus(3)
    .attackBonus(1)
    .description("Mythril Armor: light and sharp")
    .build());

Hybrid Sets

A hybrid set is a subclass bonus a player earns by wearing exactly two distinct armor materials at once. The pair is unordered: {iron, diamond} and {diamond, iron} resolve to the same hybrid. Call CrafticsAPI.registerHybridSet(entry) during onCrafticsInit().

API Method

CrafticsAPI.registerHybridSet(HybridSetEntry entry);

HybridSetEntry Builder

HybridSetEntry entry = HybridSetEntry.builder("iron", "diamond")
    .className("Warlord")
    .description("Build stacks on kill; spend them to empower your next attack.")
    .effect(HybridEffect.WARLORD)
    .build();

CrafticsAPI.registerHybridSet(entry);
Builder Method / FieldTypeRequiredDescription
builder(materialA, materialB)yesThe two armor-set material keys. The builder normalizes the pair alphabetically so order does not matter.
className(String)stringnoSubclass display name shown on the armor tooltip
description(String)stringnoOne-line mechanic description shown on the armor tooltip
effect(HybridEffect)enumyesThe combat mechanic applied during Phase 2 (see HybridEffect values below)

HybridEffect Values

Pass one of these enum constants to .effect(). Each constant maps to a distinct combat mechanic implemented internally.

HybridEffectHybridEffectHybridEffect
SKIRMISHER COUNTERPUNCHER LUCKY_STREAK
BREAKER RAMPAGE RUN_AND_GUN
SENTINEL CUTPURSE DUELIST
AMBUSH DEADEYE GILDED_GUARD
WARLORD IMMOVABLE AEGIS
GLADIATOR BERSERKER CONTAGION
STONEWALL SIEGE STORMBRINGER

JSON Datapack Schema

Place hybrid set JSON files at data/<namespace>/craftics/hybrid_sets/*.json. The three required fields are material_a, material_b, and effect.

FieldTypeRequiredDescription
material_astringyesFirst armor material key (e.g. iron)
material_bstringyesSecond armor material key (e.g. diamond)
effectstringyesHybridEffect name (case-insensitive)
class_namestringnoSubclass display name
descriptionstringnoOne-line mechanic description
{
  "material_a": "iron",
  "material_b": "diamond",
  "class_name": "Warlord",
  "description": "Build stacks on kill; spend them to empower your next attack.",
  "effect": "WARLORD"
}

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.registry.HybridSetEntry;
import com.crackedgames.craftics.combat.HybridEffect;

CrafticsAPI.registerHybridSet(HybridSetEntry.builder("iron", "diamond")
    .className("Warlord")
    .description("Build stacks on kill; spend them to empower your next attack.")
    .effect(HybridEffect.WARLORD)
    .build());

Trim Patterns

Each trim pattern grants a per-piece stat bonus and a full-set bonus activated when all four armor pieces carry the same pattern. Register custom patterns for modded trim templates. Call CrafticsAPI.registerTrimPattern(entry) during onCrafticsInit().

The pattern id must match the registry path of the vanilla trim pattern, not the full namespaced ID. For example, minecraft:sentry uses "sentry".

API Method

CrafticsAPI.registerTrimPattern(TrimPatternEntry entry);

TrimPatternEntry Record

TrimPatternEntry is a record with no builder. Construct it directly.

CrafticsAPI.registerTrimPattern(new TrimPatternEntry(
    "mymod:dragon",                      // patternId
    TrimEffects.Bonus.MELEE_POWER,       // perPieceStat
    "+1 Melee Power per piece",          // perPieceDescription
    TrimEffects.SetBonus.FERAL,          // setBonus
    "Dragon's Fury",                     // setBonusName
    "First attack each turn costs 0 AP"  // setBonusDescription
));
FieldTypeDescription
patternIdStringRegistry path of the trim pattern (e.g. "mymod:dragon")
perPieceStatTrimEffects.BonusStat bonus added once per armor piece carrying this pattern. Null means no per-piece bonus.
perPieceDescriptionStringHuman-readable text for the per-piece bonus
setBonusTrimEffects.SetBonusBonus activated when all four pieces use this pattern. Use NONE for no set bonus.
setBonusNameStringDisplay name for the set bonus
setBonusDescriptionStringHuman-readable text for the set bonus

JSON Datapack Schema

Place trim pattern JSON files at data/<namespace>/craftics/trim_patterns/*.json.

FieldTypeRequiredDescription
idstringyesRegistry path of the trim pattern
per_piece_statstringnoTrimEffects.Bonus name (case-insensitive). Omit for no per-piece bonus.
per_piece_descriptionstringnoDescription of the per-piece bonus
set_bonusstringnoTrimEffects.SetBonus name (case-insensitive). Defaults to NONE.
set_bonus_namestringnoDisplay name for the set bonus
set_bonus_descriptionstringnoDescription of the set bonus
{
  "id": "sentry",
  "per_piece_stat": "RANGED_POWER",
  "per_piece_description": "+1 Ranged Power per trimmed piece",
  "set_bonus": "OVERWATCH",
  "set_bonus_name": "Overwatch",
  "set_bonus_description": "Counter-attack ranged enemies that hit you"
}

TrimEffects.Bonus Values

BonusEffect
RANGED_POWERBonus ranged attack damage
MELEE_POWERBonus melee damage (all melee types)
SPEEDMovement speed (tiles per turn)
APExtra action points per turn
DEFENSEDamage reduction
LUCKAffects loot rolls and ability proc chances
ATTACK_RANGEExtra attack range in tiles
MAX_HPBonus maximum hit points
ARMOR_PENIgnore a portion of enemy defense
REGENHP regeneration per turn
ALLY_DAMAGEBonus damage for pet and ally attacks
STEALTH_RANGEReduced enemy detection range
SWORD_POWERBonus SLASHING damage specifically
CLEAVING_POWERBonus CLEAVING damage specifically
BLUNT_POWERBonus BLUNT damage specifically
WATER_POWERBonus WATER damage specifically
SPECIAL_POWERBonus SPECIAL damage specifically

TrimEffects.SetBonus Values

SetBonusNameEffect
NONENo set bonus
OVERWATCHSentryCounter-attack ranged enemies that hit you
SANDSTORMDuneEnemies within 2 tiles lose 1 Speed
TIDALCoastWater tiles heal 1 HP per turn
FERALWildKill streak: 1.3x damage per streak level (resets if no kills on your turn)
FORTRESSWard50% less damage when you did not move this turn
ALL_SEEINGEyeRanged attacks have +30% crit chance
ETHEREALVex20% chance to dodge incoming attacks
OCEAN_BLESSINGTideFull heal when dropping below 25% HP (once per combat)
BRUTE_FORCESnoutMelee attacks splash to adjacent enemies
INFERNALRibFire attacks deal +3 bonus damage
FORTUNE_PEAKSpireDouble emerald rewards
PATHFINDERWayfinderMovement ignores obstacle tiles
TERRAFORMERShaperMoving 3 or more tiles deals 2 damage to all enemies adjacent to your destination
PHANTOMSilenceInvisible for the first 2 turns (enemies do not act)
RALLYRaiserTamed allies get +2 Speed and +1 Attack
SYMBIOTEHostHeal 1 HP for each enemy killed
CURRENTFlowKilling an enemy refunds 1 AP
THUNDERSTRIKEBoltCritical hits stun the target for 1 turn

Trim Materials

Each trim material grants a per-piece stat bonus stacking once for each armor piece that carries a trim using that material. Register custom materials for modded ingots. Call CrafticsAPI.registerTrimMaterial(entry) during onCrafticsInit().

The material id must match the registry path of the trim material, not the full namespaced ID. For example, minecraft:iron uses "iron".

API Method

CrafticsAPI.registerTrimMaterial(TrimMaterialEntry entry);

TrimMaterialEntry Record

TrimMaterialEntry is a record with no builder. Construct it directly.

CrafticsAPI.registerTrimMaterial(new TrimMaterialEntry(
    "mymod:orichalcum",          // materialId
    TrimEffects.Bonus.ARMOR_PEN, // stat
    2,                           // valuePerPiece
    "+2 Armor Penetration per piece"
));
FieldTypeDescription
materialIdStringRegistry path of the trim material
statTrimEffects.BonusStat bonus type
valuePerPieceintAmount of the stat bonus added per trimmed armor piece
descriptionStringHuman-readable description shown in the armor tooltip

JSON Datapack Schema

Place trim material JSON files at data/<namespace>/craftics/trim_materials/*.json.

FieldTypeRequiredDescription
idstringyesRegistry path of the trim material
statstringyesTrimEffects.Bonus name (case-insensitive). An unknown value causes the file to be skipped.
value_per_pieceintnoBonus amount per trimmed piece. Defaults to 1.
descriptionstringnoHuman-readable description
{
  "id": "iron",
  "stat": "DEFENSE",
  "value_per_piece": 1,
  "description": "+1 Defense per trimmed piece"
}

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.registry.TrimMaterialEntry;
import com.crackedgames.craftics.combat.TrimEffects;

// Orichalcum trim: +2 armor pen per trimmed piece
CrafticsAPI.registerTrimMaterial(new TrimMaterialEntry(
    "mymod:orichalcum",
    TrimEffects.Bonus.ARMOR_PEN,
    2,
    "+2 Armor Penetration per trimmed piece"
));

Enchantments

Register enchantments that contribute passive stat bonuses during Craftics combat. The handler receives an EnchantmentContext holding the enchantment level, the player, and a StatModifiers accumulator. Call CrafticsAPI.registerEnchantment(enchantmentId, handler) during onCrafticsInit().

Enchantments that modify weapon abilities, such as Sharpness or Smite, are handled by weapon ability handlers rather than this registry.

API Method

CrafticsAPI.registerEnchantment(String enchantmentId, EnchantmentEffectHandler handler);

EnchantmentEffectHandler and EnchantmentContext

@FunctionalInterface
public interface EnchantmentEffectHandler {
    void apply(EnchantmentContext ctx);
}

public class EnchantmentContext {
    public int getLevel();                  // enchantment level (1, 2, 3, ...)
    public ServerPlayerEntity getPlayer();  // the player
    public StatModifiers getModifiers();    // accumulator to add bonuses to
}

Add bonuses to the accumulator using ctx.getModifiers().add(TrimEffects.Bonus, amount). The full list of available TrimEffects.Bonus values is in the Trim Patterns section above.

JSON Datapack Schema

Place enchantment JSON files at data/<namespace>/craftics/enchantments/*.json. Each bonus entry grants (enchantLevel / per_levels) * amount of the specified stat.

FieldTypeRequiredDescription
enchantmentstringyesFull enchantment registry ID, e.g. minecraft:protection
bonusesarraynoList of stat bonus objects
bonuses[].statstringyesTrimEffects.Bonus name (case-insensitive)
bonuses[].amountintnoBonus amount per qualifying level. Defaults to 1.
bonuses[].per_levelsintnoHow many enchantment levels are needed per amount. Defaults to 1 (one bonus per level).
{
  "enchantment": "minecraft:protection",
  "bonuses": [
    { "stat": "DEFENSE", "amount": 1, "per_levels": 2 }
  ]
}

With per_levels: 2 and amount: 1, Protection IV (level 4) grants (4 / 2) * 1 = 2 Defense.

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.combat.TrimEffects;

// Holy Blessing: +1 Defense and +1 Regen per enchantment level
CrafticsAPI.registerEnchantment("mymod:holy_blessing", (ctx) -> {
    ctx.getModifiers().add(TrimEffects.Bonus.DEFENSE, ctx.getLevel());
    ctx.getModifiers().add(TrimEffects.Bonus.REGEN, ctx.getLevel());
});

// Swiftness: +1 Speed regardless of level
CrafticsAPI.registerEnchantment("mymod:swiftness", (ctx) -> {
    ctx.getModifiers().add(TrimEffects.Bonus.SPEED, 1);
});

Equipment Scanners

Equipment scanners let addon mods contribute stat bonuses from non-standard inventory slots, such as trinkets, baubles, or curio slots, into Craftics combat stats. The scanner is called during combat stat calculation and its results are merged with trim and armor bonuses. Call CrafticsAPI.registerEquipmentScanner(id, scanner) during onCrafticsInit().

This registry is code-only; no JSON datapack loader exists for scanners. The scanner function must be provided in Java.

API Method

CrafticsAPI.registerEquipmentScanner(String id, EquipmentScanner scanner);

EquipmentScanner Interface

@FunctionalInterface
public interface EquipmentScanner {
    StatModifiers scan(ServerPlayerEntity player);
}

StatModifiers Class

StatModifiers accumulates bonuses using the TrimEffects.Bonus enum as keys. It can also carry an optional set bonus and custom combat effect handlers.

StatModifiers mods = new StatModifiers();
mods.add(TrimEffects.Bonus.DEFENSE, 2);       // +2 Defense
mods.add(TrimEffects.Bonus.SPEED, 1);         // +1 Speed
mods.add(TrimEffects.Bonus.MAX_HP, 4);        // +4 Max HP
mods.addSetBonus(TrimEffects.SetBonus.FERAL, "Feral Ring Set"); // optional set bonus

Call mods.addCombatEffect(name, handler) to register a dynamic CombatEffectHandler alongside the stat bonuses. Combat effect handlers are covered on the Items & Effects page.

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.EquipmentScanner;
import com.crackedgames.craftics.api.StatModifiers;
import com.crackedgames.craftics.combat.TrimEffects;
import net.minecraft.item.ItemStack;

CrafticsAPI.registerEquipmentScanner("mymod", (player) -> {
    StatModifiers mods = new StatModifiers();

    // Read items from your addon's custom equipment slots
    ItemStack belt = getCustomSlot(player, "belt");
    if (!belt.isEmpty()) {
        if (belt.isOf(MY_SWIFT_BELT)) {
            mods.add(TrimEffects.Bonus.SPEED, 1);
        } else if (belt.isOf(MY_IRON_BELT)) {
            mods.add(TrimEffects.Bonus.DEFENSE, 2);
        }
    }

    ItemStack amulet = getCustomSlot(player, "amulet");
    if (!amulet.isEmpty() && amulet.isOf(MY_LUCKY_CHARM)) {
        mods.add(TrimEffects.Bonus.LUCK, 3);
    }

    return mods;
});

Affinity Reskins

Craftics has eight affinities - Slashing, Cleaving, Blunt, Ranged, Water, Special, Pet, Physical. They are the axes a player spends level-up points on.

The count is fixed on purpose. The level-up and respec screens are laid out for exactly eight, and a mod that added ten more would not get a bigger screen, it would get an unreadable one. What a total-conversion mod usually needs is not more axes but different ones, so the eight slots can be renamed and re-iconed instead.

Reskinning

CrafticsAPI.reskinAffinity(PlayerProgression.Affinity.SLASHING,
    AffinitySkin.of("Physical", "§c⚔", "+3 dmg to physical moves"));

CrafticsAPI.reskinAffinity(PlayerProgression.Affinity.SPECIAL,
    AffinitySkin.of("Special", "§d✨", "+3 dmg to special moves"));

// Rename only, keeping Craftics' icon and description:
CrafticsAPI.reskinAffinity(PlayerProgression.Affinity.PET,
    AffinitySkin.named("Ally"));
FactoryReplaces
AffinitySkin.of(name, icon, description)All three.
AffinitySkin.named(name)The name only; icon and description keep Craftics' originals.
new AffinitySkin(name, icon, description)Any field passed null keeps the original.

What a reskin reaches

One call renames the affinity everywhere a player can see it. That is the contract, not a best effort - every display site in Craftics reads through the skin registry rather than the enum field:

The damage type is renamed with it. Each affinity has a one-to-one damage type, and a player sees both. Renaming the Slashing affinity to "Physical" while weapon tooltips still said "Slashing damage" would read as a bug rather than a theme, so the damage type follows its affinity automatically.

Register from a CrafticsAddon, not a plain initializer. Addon entrypoints run in common initialization, so the server and the client both learn the skin. A server-only registration renames the chat lines and leaves every screen showing the old name - the exact half-applied state that looks like a Craftics bug.

What does not change

Nothing mechanical. A reskinned Slashing affinity still boosts Slashing weapons, still grants its sweep chance, and still occupies the same save slot - so a reskin can be added to or removed from an existing world without touching player progress. Affinity points are saved by enum name, not by display name.

If what you want is a new axis of effectiveness rather than a renamed one, that is Attack Types - they are unlimited, and unlike affinities nobody levels them.

Combat Tools

Craftics already treats one item as a button rather than an item. The Move item is created when a fight starts, locked to a slot the player chose, restocked if it goes missing, prevented from being dropped, and destroyed when the fight ends. It is a control that happens to live in the hotbar.

A mod whose fights are commanded rather than swung needs more of those. Picking a move, switching a creature, checking a matchup - each is a control, and a control the player has to remember to carry is a control they will lose. A registered CombatTool gets Move's treatment exactly.

Registering a tool

CrafticsAPI.registerCombatTool(CombatTool.builder("mymod:lead", MyItems.LEAD)
    .order(1)                          // one slot right of Move
    .onUse((player, combat) -> {
        MyMoveMenu.open(player);        // your screen, your rules
        return true;                    // handled; no default item use
    })
    .build());
Builder MethodDefaultDescription
builder(id, item) required Unique id and the item to pin. Give it a distinct item - a tool sharing an item with ordinary gear would see that gear stripped along with the tool.
order(int) 1 How far right of the Move slot it sits, wrapping the hotbar. 1 is immediately adjacent.
stripOutsideCombat(boolean) true Destroy every copy when no fight is running. True for a control - a button that survives into the hub is an item the player can hoard, trade or lose.
onUse(handler) none Fired server-side on a mid-fight right-click. Return true to suppress the item's ordinary use.

Opening a menu

Craftics ships no menu framework here, deliberately. onUse fires and Craftics does nothing else with the click. From there you open whatever you like - a vanilla ScreenHandler, or your own payload to your own client screen. A move-selection screen is your design, and anything Craftics invented would fit it worse. This is the same split the bench uses: Craftics owns what an action means, the addon owns the screen the player picks from.

Behaviour worth knowing

Auto-Integration

Modded weapons and armor that nobody registered are given combat stats worked out from the item itself. You do not have to do anything for this, and it is why a weapon pack is playable the moment it is installed rather than after somebody writes a compat module for it.

An explicit registration always wins. Inference only ever fills a gap, so registering a weapon or an armor set - from code or from a datapack - is how you correct a guess, and you are never competing with it.

Weapons

Damage comes from the item's own vanilla attack rating, placed on Craftics' ladder for that weapon family by interpolation. It is not a formula: Craftics' numbers are hand-tuned and do not track vanilla, so a modded sword sitting between iron and diamond in vanilla terms simply gets a Craftics number between iron and diamond. The ladder is read from the live vanilla items at runtime, so it follows your config and your Minecraft version rather than a hardcoded table, and a weapon far above netherite is clamped rather than extrapolated.

Shape comes from the item's name, and decides damage type, AP cost, reach and signature trick. The families are Craftics' own, so a modded halberd behaves like a Craftics halberd:

Reads asFrom names likeTypeAPReach
Daggerdagger, knife, sai, tanto, kris, dirkSlashing11
Light bladesword, katana, rapier, cutlass, scimitar, falchionSlashing11
WarglaivewarglaiveCleaving11
Axeaxe, battleaxe, cleaver, hatchetCleaving21
GreataxegreataxeCleaving31
Greatbladegreatsword, claymore, zweihander, flambergeCleaving31
Spearspear, pike, lance, javelin, trident, harpoonSlashing22
Polearmhalberd, glaive, naginata, bardiche, guisarmeCleaving22
ScythescytheCleaving22
Hammermace, club, flail, morningstar, cudgelBlunt21
Greathammerwarhammer, maul, sledgeBlunt31
Thrownchakram, boomerang, shuriken, discusRanged13
Bowbow, crossbow, sling, musket, blowgunRanged14

Compound names win over the words inside them, so a greataxe is not an axe and a warglaive is not a glaive. Tools are excluded outright - a modded pickaxe holds "axe" and does not become a battleaxe. Anything whose name reads as no weapon at all, or that carries no attack modifier, is left alone rather than guessed at.

Armor

Armor Class comes from the piece's own armor rating and toughness, placed on the ladder for its own slot - slots are not interchangeable, so a chestplate is measured against chestplates. Toughness counts, because diamond and netherite carry identical armor points and differ only in it.

An inferred set also gets an affinity, chosen from the material's name, because every Craftics armor set grants one and a set granting none reads as broken next to the rest:

Material names likeAffinityMatching vanilla set
leather, hide, fur, cloth, woolPhysicalLeather
chainmail, chain, mail, scale, brigandineSlashingChainmail
iron, steel, titanium, obsidian, plate, mithrilCleavingIron
gold, brass, bronze, electrum, silverSpecialGold
diamond, crystal, ruby, amethyst, quartzBluntDiamond
turtle, prismarine, nautilus, coral, kelpWaterTurtle
anything elsePhysical-

A modded variant of a vanilla material lands on the affinity the player already associates with it - "reinforced iron" boosts Cleaving exactly like iron does. Inferred sets get the affinity and the Armor Class and nothing else: no flat AP, speed or attack bonuses, since those are what make a vanilla set feel special and handing them to every material would flatten the difference rather than honour it.

Food needed none of this. Any edible item has always had its heal value read from its own nutrition and saturation, so modded food has worked since long before this existed.

Auto-integration can be turned off entirely with autoIntegrateModdedGear in the config, which restores the old behaviour: unregistered weapons hit for a bare fist, unregistered armor is worth no Armor Class.

Combat Portraits

Craftics picks a combatant's head icon by entity type. That works when a type names a creature and fails completely when one entity type stands in for hundreds: every combatant in the fight shares one registry id, so any icon registered for it would be right for one creature and wrong for all the others. The HUD falls back to a coloured square with a letter in it.

It is the icon-side version of what AI keys already solve on the server. No texture can fix it, so the hook hands over the drawing instead.

// From your own ClientModInitializer - this half of the API is client-side.
CrafticsClientAPI.registerPortraitRenderer(
    (ctx, entityId, typeId, x, y, size, damageTint) -> {
        if (!typeId.equals("mymod:creature")) return false;   // not ours

        var world = MinecraftClient.getInstance().world;
        if (world == null) return false;
        if (!(world.getEntityById(entityId) instanceof MyCreature creature)) return false;

        MyRenderer.drawPortrait(ctx, creature, x, y, size, damageTint);
        return true;
    });

Why the entity id

The rosters are keyed by entity id and the creature is standing on the grid in the client world, so world.getEntityById(entityId) gets you the live entity and everything its own mod knows about it. That is usually far better than a flat image - a mod with a real model renderer can draw the same portrait its own screens use.

typeId comes with any Craftics stat suffix already stripped, and is cheap to test before doing an entity lookup.

Rules

Hiding HUD Panels

If your addon draws its own version of what a Craftics panel shows, turn the Craftics one off. A party screen that already lists the player's creatures does not want the ally roster underneath it: that is two lists of the same thing competing for one corner of the screen, and the one the player is reading is not Craftics'.

import com.crackedgames.craftics.api.HudPanel;

@Override
public void onCrafticsInit() {
    // Our party UI is the ally list.
    CrafticsAPI.hideHudPanel(HudPanel.ALLY_ROSTER);
}
PanelWhat it draws
ALLY_ROSTERTop-left list of your allies: portrait, name and HP bar each.
ENEMY_ROSTERTop-right column of remaining enemies, and the boss HP bar above it.
TURN_ORDERTop-center strip showing whose turn it is and who acts next.
PLAYER_STATUSTop-left panel with the player's own HP, AP and movement.

What suppression does and does not do

Players have their own toggles for the two rosters under the config screen's Visual section, so a player who finds a panel redundant can turn it off without an addon.