-
Notifications
You must be signed in to change notification settings - Fork 109
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add a guide for Custom Enchantments #134
base: main
Are you sure you want to change the base?
Changes from all commits
2b062a2
77ef839
5c50c74
5ee87ca
718d2d8
ada2148
2c63d0d
6be0c87
fc94b8e
afb8c57
cf940fa
069653c
ac72015
4e9dc98
390a8d9
fd47ad1
0416f8c
e15d66c
6bd1c96
1cc3cb2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,62 @@ | ||||||||||||||||||||||
--- | ||||||||||||||||||||||
title: Custom Enchantment Effects | ||||||||||||||||||||||
description: Learn how to create your enchantment effects. | ||||||||||||||||||||||
authors: | ||||||||||||||||||||||
- krizh-p | ||||||||||||||||||||||
--- | ||||||||||||||||||||||
|
||||||||||||||||||||||
# Custom Enchantments {#custom-enchantments} | ||||||||||||||||||||||
|
||||||||||||||||||||||
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. | ||||||||||||||||||||||
Check failure on line 12 in develop/items/custom-enchantment-effects.md GitHub Actions / markdownlintTrailing spaces
|
||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
However, this guide focuses on creating custom enchantment effects that aren't supported by default. | ||||||||||||||||||||||
|
||||||||||||||||||||||
**Before continuing, please check if the default Minecraft effects can work for your needs by visiting [the Minecraft Wiki's Enchantment Effect Components page](https://minecraft.wiki/w/Enchantment_definition#Effect_components)**. The rest of the guide assumes you understand how to configure "simple" data-driven enchantments. | ||||||||||||||||||||||
Comment on lines
+12
to
+16
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
## Custom Enchantment Effects {#custom-enchantment-effects} | ||||||||||||||||||||||
|
||||||||||||||||||||||
Start by creating an `enchantment` folder, and within it create a folder `effect`. Within the `effect` folder, we'll create the `LightningEnchantmentEffect` record. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
Next, we can create a constructor and override the `EnchantmentEntityEffect` interface methods. We'll also create `CODEC` variable to encode and decode our effect; you can read more about [Codecs here](../codecs). | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
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. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
@[code transcludeWith=#entrypoint](@/reference/latest/src/main/java/com/example/docs/enchantment/effect/LightningEnchantmentEffect.java) | ||||||||||||||||||||||
|
||||||||||||||||||||||
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. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
## Registering the Enchantment Effect {#registering-the-enchantment-effect} | ||||||||||||||||||||||
|
||||||||||||||||||||||
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. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
@[code transcludeWith=#entrypoint](@/reference/latest/src/main/java/com/example/docs/enchantment/ModEnchantmentEffects.java) | ||||||||||||||||||||||
its-miroma marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||||||||||||||||||
|
||||||||||||||||||||||
## Creating the Enchantment {#creating-the-enchantment} | ||||||||||||||||||||||
|
||||||||||||||||||||||
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. | ||||||||||||||||||||||
|
||||||||||||||||||||||
@[code transcludeWith=#entrypoint](@/reference/latest/src/main/java/com/example/docs/data/EnchantmentGenerator.java) | ||||||||||||||||||||||
|
||||||||||||||||||||||
Before proceeding, you should ensure your project is configured for data generation; if you are unsure, [view the respective wiki page](https://fabricmc.net/wiki/tutorial:datagen_setup). | ||||||||||||||||||||||
|
||||||||||||||||||||||
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` class. | ||||||||||||||||||||||
|
||||||||||||||||||||||
@[code transcludeWith=#initdatagen](@/reference/latest/src/main/java/com/example/docs/FabricDocsReferenceDataGenerator.java) | ||||||||||||||||||||||
|
||||||||||||||||||||||
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: | ||||||||||||||||||||||
|
||||||||||||||||||||||
@[code](@/reference/latest/src/main/generated/data/fabric-docs-reference/enchantment/thundering.json) | ||||||||||||||||||||||
|
||||||||||||||||||||||
You should also add translations to your `en_us.json` file to give your enchantment a readable name: | ||||||||||||||||||||||
|
||||||||||||||||||||||
```json | ||||||||||||||||||||||
"enchantment.FabricDocsReference.thundering": "Thundering", | ||||||||||||||||||||||
``` | ||||||||||||||||||||||
|
||||||||||||||||||||||
You should now have a working custom enchantment effect! You can test it by enchanting a weapon with the enchantment and hitting a mob, an example is given in the following video. | ||||||||||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||||||||||||||||||
|
||||||||||||||||||||||
<VideoPlayer src="/assets/develop/enchantment-effects/thunder.webm" title="Using the Lightning Effect" /> | ||||||||||||||||||||||
krizh-p marked this conversation as resolved.
Show resolved
Hide resolved
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// 1.21 2024-07-09T13:17:21.830983 Fabric docs reference/null | ||
b0b8d77fe2f55934f146827951450686de077dac data\fabric-docs-reference\enchantment\thundering.json |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
{ | ||
"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 | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,4 @@ | ||
{ | ||
"replace": false, | ||
"values": [ | ||
"fabric-docs-reference:tater" | ||
] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
package com.example.docs.data; | ||
|
||
import java.util.concurrent.CompletableFuture; | ||
|
||
import net.minecraft.component.EnchantmentEffectComponentTypes; | ||
import net.minecraft.component.type.AttributeModifierSlot; | ||
import net.minecraft.enchantment.Enchantment; | ||
import net.minecraft.enchantment.EnchantmentLevelBasedValue; | ||
import net.minecraft.enchantment.effect.EnchantmentEffectTarget; | ||
import net.minecraft.registry.RegistryKey; | ||
import net.minecraft.registry.RegistryKeys; | ||
import net.minecraft.registry.RegistryWrapper; | ||
import net.minecraft.registry.tag.ItemTags; | ||
import net.minecraft.util.Identifier; | ||
|
||
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; | ||
import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider; | ||
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition; | ||
|
||
import com.example.docs.FabricDocsReference; | ||
import com.example.docs.enchantment.effect.LightningEnchantmentEffect; | ||
|
||
//#entrypoint | ||
public class EnchantmentGenerator extends FabricDynamicRegistryProvider { | ||
public static final RegistryKey<Enchantment> THUNDERING = EnchantmentGenerator.of("thundering"); | ||
|
||
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, 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( | ||
krizh-p marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// 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); | ||
} | ||
|
||
private static RegistryKey<Enchantment> of(String path) { | ||
Identifier id = Identifier.of(FabricDocsReference.MOD_ID, path); | ||
return RegistryKey.of(RegistryKeys.ENCHANTMENT, id); | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return "ReferenceDocEnchantmentGenerator"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package com.example.docs.enchantment; | ||
|
||
import net.fabricmc.api.ModInitializer; | ||
|
||
public class FabricDocsReferenceEnchantments implements ModInitializer { | ||
@Override | ||
public void onInitialize() { | ||
ModEnchantmentEffects.registerModEnchantmentEffects(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package com.example.docs.enchantment; | ||
|
||
import com.mojang.serialization.MapCodec; | ||
|
||
import net.minecraft.enchantment.effect.EnchantmentEntityEffect; | ||
import net.minecraft.registry.Registries; | ||
import net.minecraft.registry.Registry; | ||
import net.minecraft.util.Identifier; | ||
|
||
import com.example.docs.FabricDocsReference; | ||
import com.example.docs.enchantment.effect.LightningEnchantmentEffect; | ||
|
||
//#entrypoint | ||
public class ModEnchantmentEffects { | ||
public static MapCodec<LightningEnchantmentEffect> LIGHTNING_EFFECT = register("lightning_effect", LightningEnchantmentEffect.CODEC); | ||
|
||
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); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.example.docs.enchantment.effect; | ||
|
||
import com.mojang.serialization.MapCodec; | ||
import com.mojang.serialization.codecs.RecordCodecBuilder; | ||
|
||
import net.minecraft.enchantment.EnchantmentEffectContext; | ||
import net.minecraft.enchantment.EnchantmentLevelBasedValue; | ||
import net.minecraft.enchantment.effect.EnchantmentEntityEffect; | ||
import net.minecraft.entity.Entity; | ||
import net.minecraft.entity.EntityType; | ||
import net.minecraft.entity.LivingEntity; | ||
import net.minecraft.entity.SpawnReason; | ||
import net.minecraft.entity.player.PlayerEntity; | ||
import net.minecraft.server.world.ServerWorld; | ||
import net.minecraft.util.math.BlockPos; | ||
import net.minecraft.util.math.Vec3d; | ||
|
||
//#entrypoint | ||
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) { | ||
krizh-p marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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; | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this have to be mentioned here? The plugin already highlights it.
Again, the entire Fabric project revolves around Minecraft, no need to specify it in every page IMO.