Skip to content

Commit 54fcb23

Browse files
CelDaemonekulxam
andauthored
Separate enchantment datagen content (FabricMC#476)
* WIP work for simple json enchantments instead of datagen * Expand enchantment creation section, and add section about multiple enchantment effects * Shorten section names * Rename register method to key The method doesn't actually register anything, it only creates a typed resource key * Some last minute cleanup - Add comment to enchantment supported items parameter - Omit the reverse_knockback enchantment from the register snippet - Fix naming in enchantments datagen guide * Specify where to create the generator class * Apply suggested changes * Rename reverse knockback to rebounding, and make it a curse * Fix `unusual character in key` warning * Add docs for enchantment registry bootstrap * Add datagen enchanting table and curse sections * Rename enchantment to repulsion * Fix references to `Enchantment Table` * Fix regex * Address change requests from @ekulxam Co-authored-by: SkyNotTheLimit <159592458+ekulxam@users.noreply.github.com> --------- Co-authored-by: SkyNotTheLimit <159592458+ekulxam@users.noreply.github.com>
1 parent 2c46f6e commit 54fcb23

File tree

15 files changed

+345
-54
lines changed

15 files changed

+345
-54
lines changed

.vitepress/i18n.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export const getResolver = (file: string, locale: string, warn = true): ((k: str
3131

3232
if (warn && locale === "en_us") {
3333
for (const fileK of Object.keys(strings)) {
34-
if (!/^[a-z0-9_.]+$/.test(fileK)) {
34+
if (!/^(\/\/)?[a-z0-9_.]*$/.test(fileK)) {
3535
console.warn(`${file}: unusual character in key: ${fileK}`);
3636
}
3737
}

.vitepress/sidebars/develop.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,10 @@ export default [
267267
text: "develop.data_generation.loot_tables",
268268
link: "/develop/data-generation/loot-tables",
269269
},
270+
{
271+
text: "develop.data_generation.enchantments",
272+
link: "/develop/data-generation/enchantments",
273+
},
270274
],
271275
},
272276
],
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
---
2+
title: Enchantment Generation
3+
description: A guide to generating enchantments via datagen.
4+
authors:
5+
- CelDaemon
6+
---
7+
8+
<!---->
9+
10+
::: info PREREQUISITES
11+
12+
Make sure you've completed the [datagen setup](./setup) process first.
13+
14+
:::
15+
16+
## Setup {#setup}
17+
18+
Before implementing the generator, create the `enchantment` package in the main source set and add the `ModEnchantments` class to it. Then add the `key` method to this new class.
19+
20+
@[code transcludeWith=:::key-helper](@/reference/latest/src/main/java/com/example/docs/enchantment/ModEnchantments.java)
21+
22+
Use this method to create a `ResourceKey` for your enchantment.
23+
24+
@[code transcludeWith=:::register-enchantment](@/reference/latest/src/main/java/com/example/docs/enchantment/ModEnchantments.java)
25+
26+
Now, we're ready to add the generator. In the datagen package, create a class that extends `FabricDynamicRegistryProvider`. In this newly created class, add a constructor that matches `super`, and implement the `configure` and `getName` methods.
27+
28+
@[code transcludeWith=:::provider](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentGenerator.java)
29+
30+
Then, add the `register` helper method to the newly created class.
31+
32+
@[code transcludeWith=:::register-helper](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentGenerator.java)
33+
34+
Now add the `bootstrap` method. Here, we will be registering the enchantments we want to add to the game.
35+
36+
@[code transcludeWith=:::bootstrap](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentGenerator.java)
37+
38+
In your `DataGeneratorEntrypoint`, override the `buildRegistry` method and register our bootstrap method.
39+
40+
@[code transcludeWith=:::datagen-enchantments:bootstrap](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModDataGenerator.java)
41+
42+
Finally, ensure your new generator is registered within the `onInitializeDataGenerator` method.
43+
44+
@[code transcludeWith=:::datagen-enchantments:register](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModDataGenerator.java)
45+
46+
## Creating the Enchantment {#creating-the-enchantment}
47+
48+
To create the definition for our custom enchantment, we will use the `register` method in our generator class.
49+
50+
Register your enchantment in the generator's `bootstrap` method, using the enchantment registered in `ModEnchantments`.
51+
52+
In this example, we will be using the enchantment effect created in [Custom Enchantment Effects](../items/custom-enchantment-effects), but you can also make use of the [vanilla enchantment effects](https://minecraft.wiki/w/Enchantment_definition#Effect_components).
53+
54+
@[code transcludeWith=:::register-enchantment](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentGenerator.java)
55+
56+
Now simply run data generation, and your new enchantment will be available in-game!
57+
58+
## Effect Conditions {#effect-conditions}
59+
60+
Most enchantment effect types are conditional effects. When adding these effects, it is possible to pass conditions to the `withEffect` call.
61+
62+
::: info
63+
64+
For an overview of the available condition types and their usage, see [the `Enchantments` class](https://mcsrc.dev/#1/1.21.11_unobfuscated/net/minecraft/world/item/enchantment/Enchantments#L126).
65+
66+
:::
67+
68+
@[code transcludeWith=:::effect-conditions](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentGenerator.java)
69+
70+
## Multiple Effects {#multiple-effects}
71+
72+
`withEffect` can be chained to add multiple enchantment effects to a single enchantment. However, this method requires you to specify the effect conditions for every effect.
73+
74+
To instead share the defined conditions and targets across multiple effects, `AllOf` can be used to merge them into a single effect.
75+
76+
@[code transcludeWith=:::multiple-effects](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentGenerator.java)
77+
78+
Note that the method to use depends on the type of effect being added. For example, `EnchantmentValueEffect` requires `AnyOf.valueEffects` instead. Differing effect types still require additional `withEffect` calls.
79+
80+
## Enchanting Table {#enchanting-table}
81+
82+
While we have specified the enchantment weight (or chance) in our enchantment definition, it will not appear in the enchanting table by default. To allow our enchantment to be traded by villagers and appear in the enchanting table, we need to add it to the `non_treasure` tag.
83+
84+
To do this, we can create a tag provider. Create a class that extends `FabricTagProvider<Enchantment>` in the `datagen` package. Then implement the constructor with `Registries.ENCHANTMENT` as the `registryKey` parameter to `super`, and create the `addTags` method.
85+
86+
@[code transcludeWith=:::provider](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentTagProvider.java)
87+
88+
We can now add our enchantment to `EnchantmentTags.NON_TREASURE` by calling the builder from within the `addTags` method.
89+
90+
@[code transcludeWith=:::non-treasure-tag](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentTagProvider.java)
91+
92+
## Curses {#curses}
93+
94+
Curses are also implemented using tags. We can use the tag provider from [the Enchanting Table section](#enchanting-table).
95+
96+
In the `addTags` method, simply add your enchantment to the `CURSE` tag to mark it as a curse.
97+
98+
@[code transcludeWith=:::curse-tag](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentTagProvider.java)

develop/items/custom-enchantment-effects.md

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,19 +36,19 @@ Like every other component of your mod, we'll have to add this `EnchantmentEffec
3636

3737
## Creating the Enchantment {#creating-the-enchantment}
3838

39-
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 `ExampleModEnchantmentGenerator` class.
39+
Now we have an enchantment effect! The final step is to create an enchantment that applies our custom effect. We can do this with the data-driven enchantment system by simply adding a JSON file to our mod's resources.
4040

41-
Within this class, we'll first register a new enchantment, and then use the `configure()` method to create our JSON programmatically.
41+
Create the JSON file in `data/example-mod/enchantments` folder. The name of this file will be the id of the enchantment: `thundering.json` will become `example-mod:thundering`.
4242

43-
@[code transcludeWith=#entrypoint](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModEnchantmentGenerator.java)
43+
::: info
4444

45-
Before proceeding, you should ensure your project is configured for data generation; if you are unsure, [view the respective docs page](../data-generation/setup).
45+
For more details about the file format, check out [Minecraft Wiki - Enchantment definition](https://minecraft.wiki/w/Enchantment_definition).
4646

47-
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.
47+
To quickly generate a custom enchantment, you can use the [Misode generator](https://misode.github.io/enchantment/).
4848

49-
@[code transcludeWith=:::custom-enchantments:register-generator](@/reference/latest/src/client/java/com/example/docs/datagen/ExampleModDataGenerator.java)
49+
:::
5050

51-
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:
51+
For this example we will use the following enchantment definition to add the `thundering` enchantment using our custom `lightning_effect`:
5252

5353
@[code](@/reference/latest/src/main/generated/data/example-mod/enchantment/thundering.json)
5454

reference/latest/src/client/java/com/example/docs/datagen/ExampleModDataGenerator.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
2222
FabricDataGenerator.Pack pack = fabricDataGenerator.createPack();
2323
// :::datagen-setup:pack
2424

25-
// :::custom-enchantments:register-generator
25+
// :::datagen-enchantments:register
2626
pack.addProvider(ExampleModEnchantmentGenerator::new);
27-
// :::custom-enchantments:register-generator
27+
// :::datagen-enchantments:register
2828

2929
// :::datagen-advancements:register
3030
pack.addProvider(ExampleModAdvancementProvider::new);
@@ -37,6 +37,7 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
3737
// :::datagen-tags:register
3838
pack.addProvider(ExampleModItemTagProvider::new);
3939
// :::datagen-tags:register
40+
pack.addProvider(ExampleModEnchantmentTagProvider::new);
4041

4142
// :::datagen-recipes:register
4243
pack.addProvider(ExampleModRecipeProvider::new);
@@ -64,12 +65,17 @@ public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) {
6465
}
6566

6667
// :::datagen-setup:generator
68+
// :::datagen-enchantments:bootstrap
6769
@Override
6870
public void buildRegistry(RegistrySetBuilder registryBuilder) {
71+
// :::datagen-enchantments:bootstrap
6972
registryBuilder.add(Registries.DAMAGE_TYPE, registerable -> {
7073
registerable.register(ExampleModDamageTypes.TATER_DAMAGE, TATER_DAMAGE_TYPE);
7174
});
75+
// :::datagen-enchantments:bootstrap
76+
registryBuilder.add(Registries.ENCHANTMENT, ExampleModEnchantmentGenerator::bootstrap);
7277
}
78+
// :::datagen-enchantments:bootstrap
7379

7480
// :::datagen-setup:generator
7581
}
Lines changed: 88 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,67 +1,122 @@
11
package com.example.docs.datagen;
22

3+
import java.util.List;
34
import java.util.concurrent.CompletableFuture;
45

6+
import net.minecraft.advancements.criterion.EntityFlagsPredicate;
7+
import net.minecraft.advancements.criterion.EntityPredicate;
58
import net.minecraft.core.HolderLookup;
69
import net.minecraft.core.registries.Registries;
10+
import net.minecraft.data.worldgen.BootstrapContext;
711
import net.minecraft.resources.ResourceKey;
12+
import net.minecraft.sounds.SoundEvents;
813
import net.minecraft.tags.ItemTags;
14+
import net.minecraft.util.valueproviders.ConstantFloat;
915
import net.minecraft.world.entity.EquipmentSlotGroup;
1016
import net.minecraft.world.item.enchantment.Enchantment;
1117
import net.minecraft.world.item.enchantment.EnchantmentEffectComponents;
1218
import net.minecraft.world.item.enchantment.EnchantmentTarget;
1319
import net.minecraft.world.item.enchantment.LevelBasedValue;
20+
import net.minecraft.world.item.enchantment.effects.AllOf;
21+
import net.minecraft.world.item.enchantment.effects.ApplyEntityImpulse;
22+
import net.minecraft.world.item.enchantment.effects.PlaySoundEffect;
23+
import net.minecraft.world.level.storage.loot.LootContext;
24+
import net.minecraft.world.level.storage.loot.predicates.LootItemEntityPropertyCondition;
25+
import net.minecraft.world.phys.Vec3;
1426

1527
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
1628
import net.fabricmc.fabric.api.datagen.v1.provider.FabricDynamicRegistryProvider;
17-
import net.fabricmc.fabric.api.resource.conditions.v1.ResourceCondition;
1829

19-
import com.example.docs.enchantment.ModEnchantmentEffects;
30+
import com.example.docs.enchantment.ModEnchantments;
2031
import com.example.docs.enchantment.effect.LightningEnchantmentEffect;
2132

22-
//#entrypoint
33+
// :::provider
2334
public class ExampleModEnchantmentGenerator extends FabricDynamicRegistryProvider {
2435
public ExampleModEnchantmentGenerator(FabricDataOutput output, CompletableFuture<HolderLookup.Provider> registriesFuture) {
2536
super(output, registriesFuture);
26-
System.out.println("REGISTERING ENCHANTS");
2737
}
2838

2939
@Override
3040
protected void configure(HolderLookup.Provider registries, Entries entries) {
31-
// Our new enchantment, "Thundering."
32-
register(entries, ModEnchantmentEffects.THUNDERING, Enchantment.enchantment(
33-
Enchantment.definition(
34-
registries.lookupOrThrow(Registries.ITEM).getOrThrow(ItemTags.WEAPON_ENCHANTABLE),
35-
// this is the "weight" or probability of our enchantment showing up in the table
36-
10,
37-
// the maximum level of the enchantment
38-
3,
39-
// base cost for level 1 of the enchantment, and min levels required for something higher
40-
Enchantment.dynamicCost(1, 10),
41-
// same fields as above but for max cost
42-
Enchantment.dynamicCost(1, 15),
43-
// anvil cost
44-
5,
45-
// valid slots
46-
EquipmentSlotGroup.HAND
41+
entries.addAll(registries.lookupOrThrow(Registries.ENCHANTMENT)); // Add all bootstrapped enchantments for the current mod id
42+
}
43+
44+
@Override
45+
public String getName() {
46+
return "Enchantments";
47+
}
48+
// :::provider
49+
// :::bootstrap
50+
public static void bootstrap(BootstrapContext<Enchantment> context) {
51+
// ...
52+
// :::bootstrap
53+
// :::register-enchantment
54+
register(context, ModEnchantments.THUNDERING,
55+
Enchantment.enchantment(
56+
Enchantment.definition(
57+
context.lookup(Registries.ITEM).getOrThrow(ItemTags.WEAPON_ENCHANTABLE), // The items this enchantment can be applied to
58+
10, // The weight / probability of our enchantment being available in the enchanting table
59+
3, // The max level of the enchantment
60+
Enchantment.dynamicCost(1, 10), // The base minimum cost of the enchantment, and the additional cost for every level
61+
Enchantment.dynamicCost(1, 15), // Same as the other dynamic cost, but for the maximum instead
62+
5, // The cost to apply the enchantment in an anvil, in levels
63+
EquipmentSlotGroup.HAND // The slot types in which this enchantment will be able to apply its effects
64+
)
65+
)
66+
.withEffect(
67+
EnchantmentEffectComponents.POST_ATTACK, // The type of effect to be applied
68+
EnchantmentTarget.ATTACKER, // The target to be checked for the enchantment
69+
EnchantmentTarget.VICTIM, // The target to apply the enchantment effect to
70+
new LightningEnchantmentEffect(LevelBasedValue.perLevel(0.4f, 0.2f))
71+
)
72+
);
73+
// :::register-enchantment
74+
register(context, ModEnchantments.REPULSION_CURSE,
75+
Enchantment.enchantment(
76+
Enchantment.definition(
77+
context.lookup(Registries.ITEM).getOrThrow(ItemTags.WEAPON_ENCHANTABLE),
78+
10,
79+
3,
80+
Enchantment.dynamicCost(1, 10),
81+
Enchantment.dynamicCost(1, 15),
82+
5,
83+
EquipmentSlotGroup.HAND
84+
)
4785
)
48-
)
49-
.withEffect(
50-
// enchantment occurs POST_ATTACK
86+
// :::effect-conditions
87+
.withEffect(
88+
// ...
89+
// :::effect-conditions
5190
EnchantmentEffectComponents.POST_ATTACK,
5291
EnchantmentTarget.ATTACKER,
53-
EnchantmentTarget.VICTIM,
54-
new LightningEnchantmentEffect(LevelBasedValue.perLevel(0.4f, 0.2f)) // scale the enchantment linearly.
55-
)
92+
EnchantmentTarget.ATTACKER,
93+
// :::multiple-effects
94+
AllOf.entityEffects(
95+
new ApplyEntityImpulse(new Vec3(0, 0.2, -1), new Vec3(1, 1, 1), LevelBasedValue.perLevel(0.7f, 0.2f)),
96+
new PlaySoundEffect(List.of(SoundEvents.LUNGE_1), ConstantFloat.of(5), ConstantFloat.of(1))
97+
),
98+
// :::multiple-effects
99+
// :::effect-conditions
100+
LootItemEntityPropertyCondition.hasProperties(
101+
LootContext.EntityTarget.ATTACKER,
102+
EntityPredicate.Builder.entity().flags(
103+
EntityFlagsPredicate.Builder.flags().setIsFlying(false)
104+
)
105+
)
106+
)
107+
// :::effect-conditions
56108
);
109+
// :::bootstrap
57110
}
111+
// :::bootstrap
112+
// :::provider
58113

59-
private void register(Entries entries, ResourceKey<Enchantment> key, Enchantment.Builder builder, ResourceCondition... resourceConditions) {
60-
entries.add(key, builder.build(key.identifier()), resourceConditions);
61-
}
62-
63-
@Override
64-
public String getName() {
65-
return "ExampleModEnchantmentGenerator";
114+
// :::provider
115+
// :::register-helper
116+
private static void register(BootstrapContext<Enchantment> context, ResourceKey<Enchantment> key, Enchantment.Builder builder) {
117+
context.register(key, builder.build(key.identifier()));
66118
}
119+
// :::register-helper
120+
// :::provider
67121
}
122+
// :::provider
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package com.example.docs.datagen;
2+
3+
import java.util.concurrent.CompletableFuture;
4+
5+
import net.minecraft.core.HolderLookup;
6+
import net.minecraft.core.registries.Registries;
7+
import net.minecraft.tags.EnchantmentTags;
8+
import net.minecraft.world.item.enchantment.Enchantment;
9+
10+
import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput;
11+
import net.fabricmc.fabric.api.datagen.v1.provider.FabricTagProvider;
12+
13+
import com.example.docs.enchantment.ModEnchantments;
14+
15+
// :::provider
16+
public class ExampleModEnchantmentTagProvider extends FabricTagProvider<Enchantment> {
17+
public ExampleModEnchantmentTagProvider(FabricDataOutput output, CompletableFuture<HolderLookup.Provider> registriesFuture) {
18+
super(output, Registries.ENCHANTMENT, registriesFuture);
19+
}
20+
21+
@Override
22+
protected void addTags(HolderLookup.Provider wrapperLookup) {
23+
// ...
24+
// :::provider
25+
// :::non-treasure-tag
26+
builder(EnchantmentTags.NON_TREASURE).add(ModEnchantments.THUNDERING);
27+
// :::non-treasure-tag
28+
// :::curse-tag
29+
builder(EnchantmentTags.CURSE).add(ModEnchantments.REPULSION_CURSE);
30+
// :::curse-tag
31+
// :::provider
32+
}
33+
// :::provider
34+
}
35+
// :::provider

0 commit comments

Comments
 (0)