How-to: Add Mob Affixes

Mob affixes modify living entities through attribute changes or recurring potion effects. This guide covers all three approaches: hardcoded Java, JSON datapacks, and mixed.


Option A: Hardcoded attribute affix

Attribute affixes add or multiply an entity attribute. Use them for permanent stat boosts like extra health, armor, speed, or knockback resistance.

public class MyAffixHolder extends ExileKeyHolder<ExileMobAffix> {

    public MyAffixHolder(String modid) {
        super(modid, LibDatabase.MOB_AFFIX);
    }

    public ExileKey<ExileMobAffix, KeyInfo> THICK_HIDE = ExileKey.ofId(
        this, "my_mod:thick_hide",
        x -> new AttributeMobAffix(
            ExileMobAffix.Affects.MOB,
            x.GUID(),
            1000,
            AttributeMobAffix.Data.of(
                Attributes.ARMOR,
                UUID.fromString("d4e8f1a2-b3c4-5678-9abc-def012345678"),
                AttributeModifier.Operation.ADDITION,
                new AffixNumberRange(3, 15)
            ),
            AffixTranslation.ofAttribute(Ref.MODID)
        )
    );
}

Register it via OrderedModConstructor.getAllKeyHolders():

public static final MyAffixHolder AFFIXES = new MyAffixHolder("my_mod");

@Override
public List<ExileKeyHolder> getAllKeyHolders() {
    return List.of(AFFIXES);
}

Option B: Hardcoded potion affix

Potion affixes apply a status effect to an entity on a recurring interval (every N seconds).

public ExileKey<ExileMobAffix, KeyInfo> SPEED_BURST = ExileKey.ofId(
    this, "my_mod:speed_burst",
    x -> new PotionMobAffix(
        ExileMobAffix.Affects.MOB,
        x.GUID(),
        800,
        PotionMobAffix.Data.of3sEvery10s(
            MobEffects.MOVEMENT_SPEED,
            new AffixNumberRange(1, 3)  // amplifier range
        ),
        AffixTranslation.ofPotion(Ref.MODID)
    )
);

Available factory methods for PotionMobAffix.Data:

Method Duration Reapply interval
of5sEvery10s(effect, amp) 5 s 10 s
of3sEvery10s(effect, amp) 3 s 10 s
of1sEvery10s(effect, amp) 1 s 10 s
ofPermanent(effect, amp) 10 s 10 s (effectively constant)

Option C: JSON datapack affix

Create a file in your datapack at:

src/main/resources/data/<your_namespace>/library_of_exile_mob_affix/<name>.json

Attribute affix JSON

{
  "id": "my_mod:thick_hide",
  "serializer": "attribute_mob_affix",
  "affects": "MOB",
  "weight": 1000,
  "modid": "my_mod",
  "attribute_id": "minecraft:generic.armor",
  "uuid": "d4e8f1a2-b3c4-5678-9abc-def012345678",
  "operation": "ADDITION",
  "number_range": { "min": 3.0, "max": 15.0 }
}

Potion affix JSON

{
  "id": "my_mod:speed_burst",
  "serializer": "potion_mob_affix",
  "affects": "MOB",
  "weight": 800,
  "modid": "my_mod",
  "status_effect_id": "minecraft:speed",
  "amplifer": { "min": 1.0, "max": 3.0 },
  "duration_ticks": 60,
  "apply_every_x_seconds": 10
}

JSON affixes are hot-reloaded on /reload. Hardcoded entries remain until restart.


Applying affixes to entities

Affixes are not applied automatically. You must hook ExileEvents.GRAB_MOB_AFFIXES and return the affixes you want for each entity. The system calls this at tick 3 (spawn) and every 20 ticks.

ExileEvents.GRAB_MOB_AFFIXES.register(new EventConsumer<GrabMobAffixesEvent>() {
    @Override
    public void accept(GrabMobAffixesEvent e) {
        LivingEntity entity = e.en;

        // Apply "thick_hide" to all Creepers at 50% power.
        if (entity instanceof Creeper) {
            e.add(new ExileAffixData("my_mod:thick_hide", 50));
        }

        // Apply "speed_burst" to all mobs in the Nether at a random power.
        if (entity.level().dimension() == Level.NETHER) {
            int power = entity.getRandom().nextInt(101); // 0–100
            e.add(new ExileAffixData("my_mod:speed_burst", power));
        }
    }
});

The perc field (0–100) in ExileAffixData controls where in the AffixNumberRange the stat lands. At 0% you get min; at 100% you get max.


Applying affixes to players

Set affects = Affects.PLAYER in the affix definition. The same GRAB_MOB_AFFIXES event fires for players as well. Filter with entity instanceof Player if needed.


Notes

  • Hardcoded affixes are registered during EXILE_REGISTRY_GATHER and cannot be changed at runtime without a restart.
  • JSON affixes support "loader": "REPLACE_FIELDS" to partially override a hardcoded entry.
  • Each UUID in an attribute affix must be globally unique. Reusing a UUID across two affixes on the same entity will cause one to silently override the other.