Enemies & Allies

Register reusable enemy templates, write custom AI strategies, and define combat allies that fight alongside the player.

Enemies

An enemy template is a reusable set of combat stats tied to a mob entity type. Once registered, biome JSON can reference it by id with "enemy": "<id>" instead of spelling out the same stats in every biome that uses that mob. Templates are registered with CrafticsAPI.registerEnemy().

Enemy templates can also be loaded from JSON datapacks with no Java code required. Both approaches produce the same result: a named entry in the enemy registry that biomes can reference.

Java API

Build an EnemyEntry with its fluent builder and pass it to CrafticsAPI.registerEnemy():

EnemyEntry entry = EnemyEntry.builder("mymod:desert_husk", "minecraft:husk")
    .ai("minecraft:skeleton")   // optional: use a different AI key
    .hp(10)
    .attack(3)
    .defense(1)
    .range(1)
    .speed(2)
    .build();

CrafticsAPI.registerEnemy(entry);
Builder Method Type Default Description
builder(id, entityTypeId) String, String required Registry key (e.g. "mymod:desert_husk") and the Minecraft entity type to render (e.g. "minecraft:husk"). Both are required.
ai(String) String entityTypeId Key used to look up the AI strategy in AIRegistry. Defaults to the entityTypeId, so most enemies need no explicit AI key unless you want to reuse another mob's strategy.
hp(int) int 6 Base health.
attack(int) int 2 Base attack.
defense(int) int 0 Base defense.
range(int) int 1 Attack range in tiles.
speed(int) int 0 Combat move speed in tiles per turn. 0 means use the entity type's built-in default speed. Set a positive integer to override it with a fixed value.

JSON Datapack Schema

Place JSON files at data/<namespace>/craftics/enemies/<name>.json. The id and entity fields are required. All stat fields are optional and fall back to the same defaults as the Java builder. The entity value must be a valid, loaded entity type or the file is skipped with a warning.

Field Type Required Default Description
id string yes Registry key for this template, e.g. "mymod:desert_husk".
entity string yes Minecraft entity type to render, e.g. "minecraft:husk". Must be a loaded entity type.
ai string no entity AI registry key. Resolved at runtime; unknown keys fall back to the entity type's default strategy.
hp int no 6 Base health.
attack int no 2 Base attack.
defense int no 0 Base defense.
range int no 1 Attack range in tiles.
speed int no 0 Move speed in tiles per turn. 0 means use the entity type default.

Example JSON

// data/mymod/craftics/enemies/desert_husk.json
{
  "id": "mymod:desert_husk",
  "entity": "minecraft:husk",
  "ai": "minecraft:skeleton",
  "hp": 10,
  "attack": 3,
  "defense": 1,
  "range": 1,
  "speed": 2
}

Once loaded, a biome JSON can reference this template with "enemy": "mymod:desert_husk" instead of repeating all the stat fields inline.

Tip: The entity field controls only how the mob looks in the arena. You can render a husk but give it skeleton AI, or render any modded mob entity as long as it is loaded by the time the datapack is applied. The stat block is completely independent of the entity's vanilla behavior.

Enemy AI

Craftics uses a strategy interface, EnemyAI, to decide what each enemy does on its turn. Built-in strategies cover all vanilla hostile mobs. Addon mods can register custom strategies for new mob types (or override behavior for existing ones) with CrafticsAPI.registerAI().

Registration

CrafticsAPI.registerAI("mymod:custom_zombie", (self, arena, playerPos) -> {
    // Decide what this enemy does this turn and return an EnemyAction.
    return new EnemyAction.MoveAndAttack(path, self.getAttackPower());
});

The first argument is the entity type id (or any custom string you used as the ai key in an enemy template). The second argument is an EnemyAI instance, which is a functional interface.

The EnemyAI Interface

public interface EnemyAI {
    EnemyAction decideAction(CombatEntity self, GridArena arena, GridPos playerPos);

    default Set<GridPos> computeThreatTiles(CombatEntity self, GridArena arena) {
        return null; // null = use the generic speed+range danger diamond
    }
}

decideAction is called once per enemy turn. It receives the enemy's own CombatEntity, the full GridArena (tile data, occupancy, obstacle positions), and the player's current GridPos. Return any EnemyAction record to describe what the enemy does.

computeThreatTiles is optional. Override it when the enemy's real attack reach does not match the generic danger diamond computed from speed + range. Return a set of GridPos values that Craftics will highlight red as the danger indicator for the player. Return null (the default) to use the generic formula.

AIUtils Helpers

The AIUtils class provides pathfinding and geometry helpers so you do not have to implement common patterns from scratch.

Method Description
seekOrWander(self, arena, playerPos) Universal fallback: move toward the player using pathfinding, or wander to a random adjacent tile if no path exists. Returns a Move or MoveAndAttack action, never Idle unless the enemy is completely boxed in.
wander(self, arena) Move to a random adjacent walkable tile. Use for neutral or passive mobs that are not targeting anyone.
getAdjacentTiles(arena, pos) Returns all walkable, unoccupied tiles cardinally adjacent to pos.
findBestAdjacentTarget(arena, self, playerPos, maxSteps) Finds the closest tile adjacent to the player that this entity can path to within maxSteps.
findBestAdjacentTarget(arena, self, playerPos, maxSteps, entitySize) Size-aware variant: checks that the full entity footprint fits at each candidate tile.
canPlaceFootprint(arena, anchor, entitySize) Returns true if a sized entity can occupy all footprint tiles at anchor (in-bounds, walkable, not enemy-occupied).
hasCardinalLOS(arena, from, to, maxRange) Returns true if from and to share an axis and every tile between them is clear and walkable.
getFleeTarget(arena, self, threat, maxSteps) Finds a tile 1-2 steps away from threat in the primary flee direction. Returns null if the enemy is stuck.

EnemyAction Types

EnemyAction is a sealed interface. Return one of these records from decideAction. The records marked with an asterisk carry additional fields described below the table.

Record Fields Description
MovepathMove along a list of grid positions. The enemy does not attack.
AttackdamageAttack the player from the current position.
MoveAndAttackpath, damageMove, then attack.
MoveAttackMoveapproachPath, damage, retreatPathMove in, attack, then reposition in the same turn using remaining movement.
FleepathMove away from the player. Does not attack.
IdleDo nothing this turn.
TeleporttargetInstantly relocate to a tile with no movement animation.
TeleportAndAttacktarget, damageTeleport, then immediately attack.
PouncelandingPos, damageLeap over one tile gap to land adjacent and attack.
Explodedamage, radius, blastEffectsAoE explosion centered on self. blastEffects is a list of BlastEffect(effectType, turns, amplifier) applied to the player on hit. Use the two-arg constructor for no status effects.
StartFuseBegin a fuse countdown (creeper-style). Pair with Detonate on the next turn.
DetonateResolve a pending fuse explosion.
RangedAttackdamage, effectNameAttack from range without moving (potion throw, arrow, etc.).
Swooppath, damageSweep along a line; deal damage if the player is in the path.
AttackMobtargetEntityId, damageAttack another mob (predator-prey behavior) instead of the player.
MoveAndAttackMobpath, targetEntityId, damageMove, then attack another mob.
AttackWithKnockbackdamage, knockbackTilesAttack and push the player N tiles away from the attacker.
MoveAndAttackWithKnockbackpath, damage, knockbackTilesMove, then attack with knockback.
MimicDashdirX, dirZ, damageCharge in a cardinal direction until blocked; shove players or allies in the path sideways.
MimicTantrumpath, damageHop through an ordered list of tiles; stop and deal damage if any hop lands on the player.
SummonMinionsentityTypeId, count, positions, hp, atk, defBoss action: spawn minions at the given positions.
AreaAttackcenter, radius, damage, effectNameHit all entities within radius tiles of center.
CreateTerraintiles, terrainType, durationPlace or transform terrain tiles. duration of 0 is permanent.
LineAttackstart, dx, dz, length, damageHit every tile along a line from start in direction (dx, dz).
ModifySelfstat, amount, durationBoss action: temporarily change the enemy's own stat. duration of 0 is permanent.
ForcedMovementtargetEntityId, dx, dz, tilesPush a target entity in direction (dx, dz). Use targetEntityId = -1 to target the player.
BossAbilityabilityName, resolvedAction, warningTilesTelegraph a warning this turn; the resolvedAction executes next turn.
CeilingAscendRise off the grid for one turn (spider ceiling mechanic).
CeilingDroplandingPos, damageDrop from ceiling onto a tile near the player and attack.
SpawnProjectileentityTypeId, positions, directions, hp, atk, def, projectileTypeBoss action: create traveling projectile entities.
ProjectileMovepath, impacts, impactPosAdvance a projectile; impacts = true means it collides at path end.
CompositeActionactionsExecute multiple actions in sequence in a single turn.

Supported Mob Types (Built-in)

These mobs already have registered AI strategies. You can use them in enemy templates and biome JSON without writing any code.

Example: Custom Ranged AI

import com.crackedgames.craftics.combat.ai.EnemyAI;
import com.crackedgames.craftics.combat.ai.EnemyAction;
import com.crackedgames.craftics.combat.ai.AIUtils;

// A simple ranged AI: attack in place if in range, else close the distance.
EnemyAI archerAI = (self, arena, playerPos) -> {
    int dist = self.getGridPos().manhattanDistance(playerPos);
    int range = self.getRange();

    if (dist <= range && AIUtils.hasCardinalLOS(arena, self.getGridPos(), playerPos, range)) {
        // Already in range with a clear line: shoot.
        return new EnemyAction.RangedAttack(self.getAttackPower(), "arrow");
    }

    // Out of range or blocked: move toward the player.
    return AIUtils.seekOrWander(self, arena, playerPos);
};

CrafticsAPI.registerAI("mymod:dungeon_archer", archerAI);

Allies

Combat allies are mobs recruited from the player's hub that fight alongside them in the arena. Allies have their own combat stats and can optionally scale with the owner's gear, accept a heal item during combat, or run a custom per-round effect. Allies are registered with CrafticsAPI.registerAlly().

Like enemies, allies can be defined in JSON datapacks. Datapack allies always use the default melee AI. Allies that need a custom AI strategy, a per-round hook, or programmatic stat logic must be registered through the Java API.

Java API

AllyEntry entry = AllyEntry.builder("minecraft:wolf")
    .hp(12)
    .attack(4)
    .defense(1)
    .range(1)
    .speed(3)
    .recruitMode(AllyEntry.RecruitMode.TAMED)
    .scalesWithOwnerGear(true)
    .healItem(Items.BONE, 6)
    .build();

CrafticsAPI.registerAlly(entry);
Builder Method Type Default Description
builder(entityTypeId) String required The Minecraft entity type this entry describes, e.g. "minecraft:wolf".
hp(int) int 6 Base health.
attack(int) int 1 Base attack.
defense(int) int 0 Base defense.
range(int) int 1 Attack range in tiles.
speed(int) int 2 Movement tiles per turn.
recruitMode(RecruitMode) enum TAMED How the ally is collected from the hub. See the RecruitMode table below.
ai(AllyAI) AllyAI MeleeAllyAI Combat behavior. Defaults to AllyEntry.DEFAULT_AI, which is a standard melee strategy.
scalesWithOwnerGear(boolean) boolean true When true, the ally's attack gains bonuses from the owner's armor and trim.
roundHook(AllyRoundHook) AllyRoundHook null Optional callback invoked at the start of each combat round. Signature: (CombatEntity self, EnvironmentDef environment). Use for aura effects, per-round stat changes, or environment-reactive triggers.
healItem(Item, int) Item, int null, 0 Binds a heal item: using this item on the ally during combat restores the given amount of HP.

RecruitMode Values

Value Description
TAMED The mob must be tamed and owned by the hub's player (wolves, cats, horses, and similar). Default.
BUILT Any mob of this type present in the hub yard qualifies, with no taming or ownership requirement. Use for golems and constructed mobs.
IN_COMBAT_ONLY Never recruited from the hub. The entry exists only to define combat stats for a mob tamed mid-battle.

JSON Datapack Schema

Place JSON files at data/<namespace>/craftics/allies/<name>.json. The entity field is required. Datapack allies always use the default melee AI and cannot have a per-round hook.

Field Type Required Default Description
entity string yes Minecraft entity type, e.g. "minecraft:wolf". Must be a loaded entity type.
hp int no 6 Base health.
attack int no 1 Base attack.
defense int no 0 Base defense.
range int no 1 Attack range in tiles.
speed int no 2 Movement tiles per turn.
recruit_mode string no "TAMED" Recruitment requirement. Accepts "TAMED", "BUILT", or "IN_COMBAT_ONLY" (case-insensitive). Unknown values fall back to TAMED with a warning.
scales_with_owner_gear boolean no true Whether the ally's attack gains bonuses from the owner's armor and trim.
heal_item string no Item id that heals this ally when used on it in combat, e.g. "minecraft:bone". Ignored if the item is not loaded.
heal_amount int no 0 HP restored by heal_item. Only meaningful when heal_item is set.

Example JSON

// data/mymod/craftics/allies/iron_golem.json
{
  "entity": "minecraft:iron_golem",
  "hp": 20,
  "attack": 6,
  "defense": 3,
  "range": 1,
  "speed": 1,
  "recruit_mode": "BUILT",
  "scales_with_owner_gear": false
}

Example: Custom Ally with a Round Hook

Round hooks require the Java API. The hook receives the living ally as a CombatEntity and the arena's EnvironmentDef. It is called once per round for each living ally of this type.

import com.crackedgames.craftics.api.CrafticsAPI;
import com.crackedgames.craftics.api.registry.AllyEntry;

CrafticsAPI.registerAlly(AllyEntry.builder("minecraft:cat")
    .hp(8)
    .attack(2)
    .defense(0)
    .range(1)
    .speed(3)
    .recruitMode(AllyEntry.RecruitMode.TAMED)
    .scalesWithOwnerGear(false)
    .roundHook((self, environment) -> {
        // Round hook receives the ally itself and the arena environment.
        // Use self to read or modify the ally's own combat state each round.
    })
    .build());
Note: Datapack allies always use the default melee AI (MeleeAllyAI) and cannot have a custom roundHook. If your ally needs custom combat behavior or per-round effects, register it through CrafticsAPI.registerAlly() in your onCrafticsInit() method instead.