Items & Effects

Register consumables and special-use items, define custom combat status effects, and attach lifecycle combat effect handlers to equipment scanners.

Usable Items

A usable item is any item the player can activate during their Craftics turn: a consumable draught, a thrown explosive, a support item aimed at an ally, or any other in-combat action tied to an item. Registered items are checked before Craftics' built-in item handling, so an addon can introduce entirely new items or remap an existing vanilla item without touching combat code.

Call CrafticsAPI.registerUsableItem(item, entry) from your addon's onCrafticsInit().

API Method

CrafticsAPI.registerUsableItem(Item item, UsableItemEntry entry);

UsableItemEntry Builder

UsableItemEntry entry = UsableItemEntry.builder(myItem)
    .apCost(1)                       // action points spent per use
    .range(0)                        // max tile distance to target (0 = self / no range check)
    .targetType(TargetType.SELF)     // what the player must click
    .consumedOnUse(true)             // whether one of the item is consumed on success
    .handler(myHandler)              // the effect to run (required)
    .build();

CrafticsAPI.registerUsableItem(myItem, entry);
Builder Method Type Default Description
apCost(int)int1Action points spent per use
range(int)int0Max tile distance to the target. 0 means self or no range check.
targetType(TargetType)enumSELFWhat the item targets (see TargetType values below)
consumedOnUse(boolean)booltrueWhether one of the item is consumed on a successful use
handler(UsableItemHandler)handlerThe effect to run. Required.

TargetType Values

Craftics uses the TargetType to validate the player's click and to highlight valid tiles before the handler runs.

ValueDescription
SELFNo targeting required. The item acts on the player only (food, a self-buff).
SINGLE_ENEMYA single hostile combatant standing on the targeted tile.
SINGLE_ALLYA single allied combatant (pet, party member) on the targeted tile.
ANY_TILEAny tile within range, occupied or not. Use for placing hazards or teleporting.
AOEAn area centered on the targeted tile.

UsableItemHandler Interface

UsableItemHandler is a @FunctionalInterface. Implement it directly for custom scripted logic, or compose built-in building blocks from ItemEffects. The handler receives a UsableItemContext and returns an ItemUseResult.

@FunctionalInterface
public interface UsableItemHandler {
    ItemUseResult use(UsableItemContext ctx);

    // Chain with another handler (second runs only if first succeeds):
    default UsableItemHandler and(UsableItemHandler next);
}

ItemUseResult

Return one of two factory values. On ok(), Craftics spends the AP cost and optionally consumes the item. On fail(reason), nothing is spent and the reason is shown to the player.

ItemUseResult.ok()              // item used successfully
ItemUseResult.fail("No target there.")  // item failed, keep AP

UsableItemContext

The context exposes the combat state and a set of typed operations so handlers never need to touch Craftics internals.

MethodReturnsDescription
player()ServerPlayerEntityThe player using the item
arena()GridArenaThe active arena
targetPos()GridPosThe tile the player targeted (player's tile for SELF items)
targetEntity()CombatEntity or nullThe combatant on the targeted tile, or null if empty
stack()ItemStackThe item stack being used
combatants()List<CombatEntity>All combatants (enemies and allies). Read-only.
damage(target, amount)intDeal damage through the target's defense. Returns actual damage dealt.
healPlayer(amount)voidHeal the player (capped at max health)
healEntity(entity, amount)voidHeal a combatant (capped at max HP)
applyPlayerEffect(type, turns, amplifier)voidApply a built-in status effect to the player
applyEffect(target, type, turns, amplifier)voidApply a built-in status effect to a combatant
applyCustomEffect(target, effectId, turns, amplifier)voidApply a registered custom status effect to a combatant. Reserved for a future SDK phase, currently a no-op.
stun(target)voidStun a combatant so it loses its next turn
knockback(target, distance)voidPush a combatant up to N tiles away from the player
teleportPlayer(pos)voidTeleport the player to a walkable, unoccupied tile
placeTileEffect(pos, effectType)voidPlace a tile effect (e.g. "lava", "campfire", "poison_cloud")
message(text)voidSend a chat message to the player

Built-in Effect Factories (ItemEffects class)

The ItemEffects class provides static factory methods that return composable UsableItemHandler building blocks. Chain them with .and(). Blocks that need a combatant on the targeted tile fail cleanly when the tile is empty, returning the AP to the player.

Factory MethodDescription
heal(amount)Heal the player by amount.
applyToSelf(type, turns, amplifier)Apply a built-in status effect to the player.
damageTarget(amount)Deal amount damage to the combatant on the targeted tile. Fails if the tile is empty.
applyToTarget(type, turns, amplifier)Apply a built-in status effect to the combatant on the targeted tile. Fails if the tile is empty.
stunTarget()Stun the combatant on the targeted tile. Fails if the tile is empty.
knockbackTarget(distance)Push the combatant on the targeted tile up to distance tiles away. Fails if the tile is empty.
aoeDamage(radius, amount)Deal amount damage to every enemy within radius tiles of the target tile (Manhattan distance).
teleportToTarget()Teleport the player onto the targeted tile.
placeTile(effectType)Place a tile effect on the targeted tile.
message(text)Send a chat message to the player.

JSON Datapack Schema

Place usable item JSON files at data/<namespace>/craftics/items/*.json. The effects array is an ordered list of building blocks that run in sequence. Each block must succeed for the next to run. Items needing scripted logic must be registered through CrafticsAPI.registerUsableItem in Java.

FieldTypeRequiredDescription
itemstringyesFull item registry ID, e.g. minecraft:golden_apple
ap_costintnoAction points spent per use. Defaults to 1.
rangeintnoMax tile distance to the target. Defaults to 0 (self / no range check).
targetstringnoTargetType name (case-insensitive). Defaults to SELF.
consumedboolnoWhether one of the item is consumed on success. Defaults to true.
effectsarrayyesOrdered list of effect objects (see effect kinds below).

Effect object kind values and their extra fields:

kindExtra Fields
healamount (int, default 1)
damage_targetamount (int, default 1)
stun_target(none)
knockback_targetdistance (int, default 1)
aoe_damageradius (int, default 1), amount (int, default 1)
teleport(none)
place_tiletile (string, e.g. "lava")
messagetext (string)
apply_selfeffect (EffectType name), turns (int, default 3), amplifier (int, default 0)
apply_targeteffect (EffectType name), turns (int, default 3), amplifier (int, default 0)
{
  "item": "minecraft:golden_apple",
  "ap_cost": 1,
  "range": 0,
  "target": "SELF",
  "consumed": true,
  "effects": [
    { "kind": "heal", "amount": 4 },
    { "kind": "apply_self", "effect": "ABSORPTION", "turns": 4, "amplifier": 0 }
  ]
}

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.ItemEffects;
import com.crackedgames.craftics.api.TargetType;
import com.crackedgames.craftics.api.registry.UsableItemEntry;
import com.crackedgames.craftics.combat.CombatEffects;

// Healing draught: heal 8 HP and apply Absorption for 4 turns
CrafticsAPI.registerUsableItem(MyItems.HEALING_DRAUGHT,
    UsableItemEntry.builder(MyItems.HEALING_DRAUGHT)
        .apCost(1)
        .targetType(TargetType.SELF)
        .consumedOnUse(true)
        .handler(ItemEffects.heal(8)
            .and(ItemEffects.applyToSelf(CombatEffects.EffectType.ABSORPTION, 4, 0)))
        .build());

// Poison vial: deal 4 damage and apply Poison to a single enemy
CrafticsAPI.registerUsableItem(MyItems.POISON_VIAL,
    UsableItemEntry.builder(MyItems.POISON_VIAL)
        .apCost(1)
        .range(3)
        .targetType(TargetType.SINGLE_ENEMY)
        .consumedOnUse(true)
        .handler(ItemEffects.damageTarget(4)
            .and(ItemEffects.applyToTarget(CombatEffects.EffectType.POISON, 3, 0)))
        .build());

Custom Status Effects

Craftics has 23 built-in status effects keyed by the CombatEffects.EffectType enum. Custom effects extend that set with a string id instead of an enum constant, so addons can introduce their own debuffs and buffs without forking the mod. A custom effect ticks once per round for each combatant carrying it: the declarative hpChangePerTurn is applied first, then the optional CustomEffectTickHandler runs for scripted logic.

Call CrafticsAPI.registerEffect(def) from your addon's onCrafticsInit(). A registered effect ticks once per round on any combatant carrying it. UsableItemContext.applyCustomEffect, the planned way for an item to place a custom effect on a combatant, is currently a no-op reserved for a future SDK phase.

API Method

CrafticsAPI.registerEffect(CustomEffectDef def);

CustomEffectDef Builder

CustomEffectDef def = CustomEffectDef.builder("mymod:frostbite")
    .displayName("Frostbite")
    .description("-2 HP per turn")
    .colorCode("§b")
    .harmful(true)
    .hpChangePerTurn(-2)
    .tickHandler(myTickHandler)    // optional
    .build();

CrafticsAPI.registerEffect(def);
Builder Method Type Default Description
builder(id)stringUnique effect id, e.g. "mymod:frostbite". Required.
displayName(String)stringidName shown to players
description(String)string""Short description for tooltips and guides
colorCode(String)string"§7"Minecraft section-sign color code for messages and the effect display
harmful(boolean)booltrueWhether this is a debuff (vs. a buff)
hpChangePerTurn(int)int0Per-turn HP change, scaled by (amplifier + 1). Negative damages, positive heals. Use 0 for no automatic HP change.
tickHandler(CustomEffectTickHandler)handlernullOptional scripted per-turn logic (see below)

CustomEffectTickHandler Interface

CustomEffectTickHandler is a @FunctionalInterface. It runs once per round, per combatant carrying the effect, after hpChangePerTurn has already been applied. Effects that only need a flat HP change do not need a handler at all.

@FunctionalInterface
public interface CustomEffectTickHandler {
    void onTick(CombatEntity entity, int amplifier, int turnsRemaining);
}
ParameterDescription
entityThe combatant carrying the effect
amplifierThe effect's current amplifier (0 = level I)
turnsRemainingTurns remaining including the current one

JSON Datapack Schema

Place status effect JSON files at data/<namespace>/craftics/effects/*.json. Datapack effects are declarative: they support a flat per-turn HP change but no scripted tick logic. Effects needing a CustomEffectTickHandler must be registered through CrafticsAPI.registerEffect in Java.

FieldTypeRequiredDescription
idstringyesUnique effect id, e.g. "mymod:frostbite"
namestringnoDisplay name shown to players. Defaults to the id.
descriptionstringnoShort description for tooltips
colorstringnoMinecraft section-sign color code. Defaults to "§7".
harmfulboolnoWhether this is a debuff. Defaults to true.
hp_change_per_turnintnoPer-turn HP change, scaled by (amplifier + 1). Negative damages, positive heals. Defaults to 0.
{
  "id": "mymod:frostbite",
  "name": "Frostbite",
  "description": "-2 HP per turn",
  "color": "§b",
  "harmful": true,
  "hp_change_per_turn": -2
}

Code Example

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.CustomEffectDef;

// Frostbite: a simple damage-over-time debuff, registered from a datapack or in code
CrafticsAPI.registerEffect(CustomEffectDef.builder("mymod:frostbite")
    .displayName("Frostbite")
    .description("-2 HP per turn")
    .colorCode("§b")
    .harmful(true)
    .hpChangePerTurn(-2)
    .build());

// Festering Wound: a damage-over-time effect that flares up as it fades.
// A tick handler receives only the carrying entity, its amplifier, and the
// turns remaining, so it acts on that entity.
CrafticsAPI.registerEffect(CustomEffectDef.builder("mymod:festering_wound")
    .displayName("Festering Wound")
    .description("-1 HP per turn, with a burst of damage as it fades")
    .colorCode("§2")
    .harmful(true)
    .hpChangePerTurn(-1)
    .tickHandler((entity, amplifier, turnsRemaining) -> {
        // Scripted logic runs after the flat hpChangePerTurn is applied.
        if (turnsRemaining <= 1) {
            entity.takeDamage(3 + amplifier);
        }
    })
    .build());

Combat Effect Handlers

Combat effect handlers are lifecycle callbacks that fire at specific moments during a Craftics combat encounter. Unlike flat stat bonuses, they can react to events such as taking damage, killing an enemy, or moving on the grid, and they can intercept and modify combat values. They are the mechanism behind trinket effects, ring abilities, set bonuses from custom equipment, and any other dynamic in-combat behavior an addon needs.

Handlers are registered through StatModifiers.addCombatEffect(name, handler) inside an EquipmentScanner. The scanner runs at the start of each combat encounter and produces fresh handler instances, so instance fields reset naturally between fights.

Registration

There is no CrafticsAPI.registerCombatEffect method. Instead, combat effect handlers live alongside stat bonuses in a StatModifiers object returned by an EquipmentScanner. That scanner is registered once via CrafticsAPI.registerEquipmentScanner.

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

    ItemStack ring = getCustomSlot(player, "ring");
    if (ring.isOf(MY_THORNS_RING)) {
        mods.addCombatEffect("Thorns Ring", new ThornsCombatEffect());
    }

    return mods;
});

Multiple effects per scanner are allowed, each with its own name and handler instance. You can also mix stat bonuses and combat effects in the same StatModifiers.

CombatEffectHandler Interface

CombatEffectHandler is an interface with default no-op implementations for every method. Override only the callbacks your effect needs.

Callback Returns When it fires
onCombatStart(ctx)voidCombat begins, before the first turn
onTurnStart(ctx)voidPlayer's turn starts
onTurnEnd(ctx)voidPlayer's turn ends, before enemies act
onCombatEnd(ctx)voidCombat finishes (victory or defeat)
onDealDamage(ctx, target, damage)CombatResultPlayer deals damage. Can modify the amount.
onDealKillingBlow(ctx, killed)voidPlayer kills an enemy
onCrit(ctx, target, damage)voidPlayer lands a critical hit
onMiss(ctx, target)voidPlayer's attack misses
onTakeDamage(ctx, attacker, damage)CombatResultPlayer takes damage. Can modify the amount.
onLethalDamage(ctx, attacker, damage)CombatResultDamage would kill the player. Can cancel to prevent death.
onDodge(ctx, attacker)voidPlayer dodges an attack
onBlocked(ctx, attacker, blockedDamage)voidPlayer's shield blocks damage
onMove(ctx, from, to, distance)voidPlayer moves on the grid
onKnockback(ctx, source, distance)CombatResultPlayer is knocked back. Can modify the distance.
onAllyAttack(ctx, ally, target, damage)voidA pet or ally deals damage
onAllyTakeDamage(ctx, ally, attacker, damage)CombatResultA pet or ally takes damage. Can modify the amount.
onAllyKill(ctx, ally, killed)voidA pet or ally kills an enemy
onAllyDeath(ctx, ally)voidA pet or ally dies
onEffectApplied(ctx, effect, turns)CombatResultA status effect is applied to the player. Can modify duration.
onEffectExpired(ctx, effect)voidA status effect expires on the player
onLootRoll(ctx, loot)voidLoot is generated after a level. Modify the mutable list directly.
onEmeraldGain(ctx, amount)CombatResultEmeralds are awarded. Can modify the amount.
onEnemySpawn(ctx, enemy)voidAn enemy spawns into the arena
onBossPhaseChange(ctx, boss, newPhase)voidA boss transitions to a new phase

CombatResult

Callbacks that return CombatResult can intercept and modify the value in question. Three factory methods are available:

CombatResult.unchanged(originalValue)       // pass through with no change
CombatResult.modify(newValue, "message")    // change the value, add a combat log message
CombatResult.cancel("message")             // cancel the action entirely (e.g., prevent death)

CombatEffectContext

All callbacks receive a CombatEffectContext as their first argument. The context is created once at combat start and updated each turn.

ctx.getPlayer()         // ServerPlayerEntity, the player
ctx.getArena()          // GridArena, the active arena
ctx.getPlayerEffects()  // CombatEffects, active status effects on the player
ctx.getTrimScan()       // TrimEffects.TrimScan, the player's trim data
ctx.getAllEnemies()     // List<CombatEntity>, all living enemies
ctx.getAllAllies()      // List<CombatEntity>, all living allies

Statefulness

Handlers are per-combat instances. The EquipmentScanner is called at combat start, producing a fresh handler each time. Instance fields therefore reset automatically between fights, and you can safely use them to track per-combat state.

mods.addCombatEffect("One-Shot Shield", new CombatEffectHandler() {
    private boolean used = false; // resets each combat

    @Override
    public CombatResult onLethalDamage(CombatEffectContext ctx, CombatEntity attacker, int damage) {
        if (!used) {
            used = true;
            ctx.getPlayer().heal(10);
            return CombatResult.cancel("§dOne-Shot Shield absorbs the blow!");
        }
        return CombatResult.unchanged(damage);
    }
});

Code Examples

Thorns Ring (reflect incoming damage)

mods.addCombatEffect("Thorns Ring", new CombatEffectHandler() {
    @Override
    public CombatResult onTakeDamage(CombatEffectContext ctx, CombatEntity attacker, int damage) {
        if (attacker != null) {
            attacker.takeDamage(Math.max(1, damage / 4));
        }
        return CombatResult.modify(damage, "§6Thorns Ring reflects damage!");
    }
});

Fortune Amulet (bonus emeralds)

mods.addCombatEffect("Fortune Amulet", new CombatEffectHandler() {
    @Override
    public CombatResult onEmeraldGain(CombatEffectContext ctx, int amount) {
        return CombatResult.modify(amount + 2, "§6Fortune Amulet: +2 bonus emeralds!");
    }
});

Death Prevention (once per combat)

mods.addCombatEffect("Lucky Charm", new CombatEffectHandler() {
    private boolean used = false;

    @Override
    public CombatResult onLethalDamage(CombatEffectContext ctx, CombatEntity attacker, int damage) {
        if (!used) {
            used = true;
            ctx.getPlayer().heal(10);
            return CombatResult.cancel("§d Lucky Charm saves you from death!");
        }
        return CombatResult.unchanged(damage);
    }
});