Skip to content
◆ TTS-STUDIO docs Discord

Plugins / EnchantsForge

Developer API

EnchantsForge provides a clean developer API for creating and registering your own custom enchantments. You can extend the base CustomEnchant class, implement any of 5 event hooks, and register your enchant via EnchantRegistry.

Renamed: EnchantsForge was previously released as "CustomEnchants". The Java package (com.cristian.customenchants), class names (CustomEnchant, EnchantRegistry, EnchantContext), and the plugin.yml depend token are all preserved — only the plugin's display name and branding changed.

Adding the dependency

Add EnchantsForge as a dependency in your plugin.yml:

# plugin.yml
name: MyPlugin
version: 1.0
depend:
  - EnchantsForge

Note: The depend: token must match the name: field in the EnchantsForge plugin.yml, which is now EnchantsForge. If your plugin was previously depending on CustomEnchants, update this token.

Add the JAR to your build path (Maven/Gradle local file dependency or use a local Maven repository).

Creating a custom enchantment

Extend the CustomEnchant abstract class and implement the required methods:

import com.cristian.customenchants.api.CustomEnchant;
import com.cristian.customenchants.api.EnchantContext;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.plugin.java.JavaPlugin;

import java.util.Set;

public class MyEnchant extends CustomEnchant {

    public MyEnchant(JavaPlugin plugin) {
        super(plugin);
    }

    // ─── Required Methods ────────────────────────────────────────

    @Override
    public NamespacedKey getKey() {
        return new NamespacedKey(plugin, "my_enchant");
    }

    @Override
    public String getId() {
        return "my_enchant";  // Used in commands: /enchant give my_enchant 1
    }

    @Override
    public String getDisplayName() {
        return "My Enchant";  // Shown in GUI and item lore
    }

    @Override
    public int getMaxLevel() {
        return 3;
    }

    @Override
    public Set<Material> getApplicableTo() {
        return Set.of(Material.DIAMOND_SWORD, Material.NETHERITE_SWORD);
    }

    // ─── Optional: Declare XP cost per level ─────────────────────

    @Override
    public int getXpCost(int level) {
        return level * 10;  // 10xp for I, 20xp for II, 30xp for III
    }

    // ─── Optional: Declare incompatible enchantments ─────────────

    @Override
    public Set<String> getConflicts() {
        return Set.of("some_other_enchant");
    }
}

Event hooks

Implement any combination of the 5 available hooks. You only need to override the hooks you want to use:

1. onMeleeHit — Melee Attack

@Override
public void onMeleeHit(EnchantContext.MeleeHit ctx) {
    // ctx.getAttacker()  — the player who attacked
    // ctx.getVictim()    — the entity that was hit
    // ctx.getLevel()     — the enchant level on the weapon
    // ctx.getDamage()    — the base damage of the hit
    // ctx.setDamage(d)   — modify the damage

    int level = ctx.getLevel();
    double heal = level * 2.0;

    // Heal the attacker
    double maxHp = ctx.getAttacker().getMaxHealth();
    double newHp = Math.min(ctx.getAttacker().getHealth() + heal, maxHp);
    ctx.getAttacker().setHealth(newHp);
}

2. onKill — Entity Kill

@Override
public void onKill(EnchantContext.Kill ctx) {
    // ctx.getKiller()    — the player who killed
    // ctx.getVictim()    — the entity that died
    // ctx.getLevel()     — enchant level

    ctx.getKiller().sendMessage("You got a kill bonus from " + getDisplayName() + "!");
}

3. onTick — Passive Tick (runs periodically)

@Override
public void onTick(EnchantContext.Tick ctx) {
    // ctx.getPlayer()    — the player wearing/holding the enchanted item
    // ctx.getLevel()     — enchant level
    // Called on a BukkitRunnable loop while the player is online

    // Example: slowly regenerate hunger
    if (ctx.getPlayer().getFoodLevel() < 20) {
        ctx.getPlayer().setFoodLevel(ctx.getPlayer().getFoodLevel() + 1);
    }
}

Note: The tick rate is managed by the base plugin. Do not create your own runnable for tick-based logic — use this hook instead.

4. onDamageTaken — Player Takes Damage

@Override
public void onDamageTaken(EnchantContext.DamageTaken ctx) {
    // ctx.getPlayer()    — the player wearing the enchanted armor
    // ctx.getLevel()     — enchant level
    // ctx.getDamage()    — incoming damage amount
    // ctx.setDamage(d)   — reduce or increase the damage

    // Example: reduce damage by 10% per level
    double reduction = ctx.getLevel() * 0.10;
    ctx.setDamage(ctx.getDamage() * (1.0 - reduction));
}

5. onProjectileHit — Projectile (Arrow) Hit

@Override
public void onProjectileHit(EnchantContext.ProjectileHit ctx) {
    // ctx.getShooter()   — the player who fired the projectile
    // ctx.getVictim()    — the entity hit (may be null if hit a block)
    // ctx.getLevel()     — enchant level
    // ctx.getProjectile()— the projectile entity

    // Example: spawn a lightning bolt on arrow hit
    if (ctx.getVictim() != null) {
        ctx.getVictim().getWorld().strikeLightning(ctx.getVictim().getLocation());
    }
}

Registering your enchantment

Register your enchantment in your plugin's onEnable() via the EnchantRegistry service:

import com.cristian.customenchants.api.EnchantRegistry;
import org.bukkit.Bukkit;
import org.bukkit.plugin.RegisteredServiceProvider;

public class MyPlugin extends JavaPlugin {

    @Override
    public void onEnable() {
        // Get the EnchantRegistry from the ServicesManager
        RegisteredServiceProvider<EnchantRegistry> provider =
            Bukkit.getServicesManager().getRegistration(EnchantRegistry.class);

        if (provider != null) {
            EnchantRegistry registry = provider.getProvider();
            registry.register(new MyEnchant(this));
            getLogger().info("Registered MyEnchant!");
        } else {
            getLogger().severe("EnchantsForge API not found! Disabling.");
            setEnabled(false);
        }
    }
}

Warning: Always check that the provider is not null — it will be null if EnchantsForge is not loaded. Make sure EnchantsForge is listed in your plugin.yml depend array.

Complete example

Here is a full minimal enchantment that poisons enemies on melee hit:

public class PoisonBlade extends CustomEnchant {

    public PoisonBlade(JavaPlugin plugin) { super(plugin); }

    @Override public NamespacedKey getKey() {
        return new NamespacedKey(plugin, "poison_blade");
    }

    @Override public String getId() { return "poison_blade"; }

    @Override public String getDisplayName() { return "Poison Blade"; }

    @Override public int getMaxLevel() { return 3; }

    @Override public Set<Material> getApplicableTo() {
        return Set.of(Material.IRON_SWORD, Material.DIAMOND_SWORD, Material.NETHERITE_SWORD);
    }

    @Override
    public void onMeleeHit(EnchantContext.MeleeHit ctx) {
        int duration = ctx.getLevel() * 40;  // 2s / 4s / 6s (in ticks)
        ctx.getVictim().addPotionEffect(
            new PotionEffect(PotionEffectType.POISON, duration, ctx.getLevel() - 1)
        );
    }
}

API summary

Interface/Class Description
CustomEnchant Base class to extend for all custom enchantments
EnchantRegistry Service for registering enchantments; obtained via ServicesManager
EnchantContext.MeleeHit Context object for onMeleeHit hook
EnchantContext.Kill Context object for onKill hook
EnchantContext.Tick Context object for onTick hook
EnchantContext.DamageTaken Context object for onDamageTaken hook
EnchantContext.ProjectileHit Context object for onProjectileHit hook

Keep reading