🇬🇧 English
🇬🇧 English
Appearance
🇬🇧 English
🇬🇧 English
Appearance
This page is written for version:
1.21
This page is written for version:
1.21
Starting from version 1.21, custom enchantments in Minecraft use a "data-driven" approach. This makes it easier to add simple enchantments, like increasing attack damage, but more challenging to create complex ones. The process involves breaking down enchantments into effect components.
An effect component contains the code that defines the special effects of an enchantment. Minecraft supports various default effects, such as item damage, knockback, and experience.
TIP
Be sure to check if the default Minecraft effects satisfy your needs by visiting the Minecraft Wiki's Enchantment Effect Components page. This guide assumes you understand how to configure "simple" data-driven enchantments and focuses on creating custom enchantment effects that aren't supported by default.
Start by creating an enchantment
folder, and within it, create an effect
folder. Within that, we'll create the LightningEnchantmentEffect
record.
Next, we can create a constructor and override the EnchantmentEntityEffect
interface methods. We'll also create a CODEC
variable to encode and decode our effect; you can read more about Codecs here.
The bulk of our code will go into the apply()
event, which is called when the criteria for your enchantment to work is met. We'll later configure this Effect
to be called when an entity is hit, but for now, let's write simple code to strike the target with lightning.
public record LightningEnchantmentEffect(EnchantmentLevelBasedValue amount) implements EnchantmentEntityEffect {
public static final MapCodec<LightningEnchantmentEffect> CODEC = RecordCodecBuilder.mapCodec(instance ->
instance.group(
EnchantmentLevelBasedValue.CODEC.fieldOf("amount").forGetter(LightningEnchantmentEffect::amount)
).apply(instance, LightningEnchantmentEffect::new)
);
@Override
public void apply(ServerWorld world, int level, EnchantmentEffectContext context, Entity target, Vec3d pos) {
if (target instanceof LivingEntity victim) {
if (context.owner() != null && context.owner() instanceof PlayerEntity player) {
float numStrikes = this.amount.getValue(level);
for (float i = 0; i < numStrikes; i++) {
BlockPos position = victim.getBlockPos();
EntityType.LIGHTNING_BOLT.spawn(world, position, SpawnReason.TRIGGERED);
}
}
}
}
@Override
public MapCodec<? extends EnchantmentEntityEffect> getCodec() {
return CODEC;
}
}
Here, the amount
variable indicates a value scaled to the level of the enchantment. We can use this to modify how effective the enchantment is based on level. In the code above, we are using the level of the enchantment to determine how many lightning strikes are spawned.
Like every other component of your mod, we'll have to add this EnchantmentEffect
to Minecraft's registry. To do so, add a class ModEnchantmentEffects
(or whatever you want to name it) and a helper method to register the enchantment. Be sure to call the registerModEnchantmentEffects()
in your main class, which contains the onInitialize()
method.
public class ModEnchantmentEffects {
public static final RegistryKey<Enchantment> THUNDERING = of("thundering");
public static MapCodec<LightningEnchantmentEffect> LIGHTNING_EFFECT = register("lightning_effect", LightningEnchantmentEffect.CODEC);
private static RegistryKey<Enchantment> of(String path) {
Identifier id = Identifier.of(FabricDocsReference.MOD_ID, path);
return RegistryKey.of(RegistryKeys.ENCHANTMENT, id);
}
private static <T extends EnchantmentEntityEffect> MapCodec<T> register(String id, MapCodec<T> codec) {
return Registry.register(Registries.ENCHANTMENT_ENTITY_EFFECT_TYPE, Identifier.of(FabricDocsReference.MOD_ID, id), codec);
}
public static void registerModEnchantmentEffects() {
FabricDocsReference.LOGGER.info("Registering EnchantmentEffects for" + FabricDocsReference.MOD_ID);
}
}
Now we have an enchantment effect! The final step is to create an enchantment that applies our custom effect. While this can be done by creating a JSON file similar to those in datapacks, this guide will show you how to generate the JSON dynamically using Fabric's data generation tools. To begin, create an EnchantmentGenerator
class.
Within this class, we'll first register a new enchantment, and then use the configure()
method to create our JSON programmatically.
public class EnchantmentGenerator extends FabricDynamicRegistryProvider {
public EnchantmentGenerator(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registriesFuture) {
super(output, registriesFuture);
System.out.println("REGISTERING ENCHANTS");
}
@Override
protected void configure(RegistryWrapper.WrapperLookup registries, Entries entries) {
// Our new enchantment, "Thundering."
register(entries, ModEnchantmentEffects.THUNDERING, Enchantment.builder(
Enchantment.definition(
registries.getWrapperOrThrow(RegistryKeys.ITEM).getOrThrow(ItemTags.WEAPON_ENCHANTABLE),
// this is the "weight" or probability of our enchantment showing up in the table
10,
// the maximum level of the enchantment
3,
// base cost for level 1 of the enchantment, and min levels required for something higher
Enchantment.leveledCost(1, 10),
// same fields as above but for max cost
Enchantment.leveledCost(1, 15),
// anvil cost
5,
// valid slots
AttributeModifierSlot.HAND
)
)
.addEffect(
// enchantment occurs POST_ATTACK
EnchantmentEffectComponentTypes.POST_ATTACK,
EnchantmentEffectTarget.ATTACKER,
EnchantmentEffectTarget.VICTIM,
new LightningEnchantmentEffect(EnchantmentLevelBasedValue.linear(0.4f, 0.2f)) // scale the enchantment linearly.
)
);
}
private void register(Entries entries, RegistryKey<Enchantment> key, Enchantment.Builder builder, ResourceCondition... resourceConditions) {
entries.add(key, builder.build(key.getValue()), resourceConditions);
}
@Override
public String getName() {
return "ReferenceDocEnchantmentGenerator";
}
}
Before proceeding, you should ensure your project is configured for data generation; if you are unsure, view the respective docs page.
Lastly, we must tell our mod to add our EnchantmentGenerator
to the list of data generation tasks. To do so, simply add the EnchantmentGenerator
to this inside of the onInitializeDataGenerator
method.
pack.addProvider(EnchantmentGenerator::new);
Now, when you run your mod's data generation task, enchantment JSONs will be generated inside the generated
folder. An example can be seen below:
{
"anvil_cost": 5,
"description": {
"translate": "enchantment.fabric-docs-reference.thundering"
},
"effects": {
"minecraft:post_attack": [
{
"affected": "victim",
"effect": {
"type": "fabric-docs-reference:lightning_effect",
"amount": {
"type": "minecraft:linear",
"base": 0.4,
"per_level_above_first": 0.2
}
},
"enchanted": "attacker"
}
]
},
"max_cost": {
"base": 1,
"per_level_above_first": 15
},
"max_level": 3,
"min_cost": {
"base": 1,
"per_level_above_first": 10
},
"slots": [
"hand"
],
"supported_items": "#minecraft:enchantable/weapon",
"weight": 10
}
You should also add translations to your en_us.json
file to give your enchantment a readable name:
"enchantment.FabricDocsReference.thundering": "Thundering",
You should now have a working custom enchantment effect! Test it by enchanting a weapon with the enchantment and hitting a mob. An example is given in the following video: