tutorial:datagen_advancements
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revision | |||
tutorial:datagen_advancements [2023/09/29 18:08] – Updated pre-custom criterion section for fabric 1.20.2 jmanc3 | tutorial:datagen_advancements [2023/10/02 23:11] (current) – Updated custom criterion section jmanc3 | ||
---|---|---|---|
Line 339: | Line 339: | ||
===== How To Make a Custom Criterion? ===== | ===== How To Make a Custom Criterion? ===== | ||
- | Our mod is keeping track of how many jumping jacks a player | + | To start, let's create a new minecraft mechanic. |
+ | |||
+ | In your '' | ||
+ | |||
+ | <code java> | ||
+ | import net.fabricmc.api.ModInitializer; | ||
+ | import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; | ||
+ | import net.minecraft.item.Item; | ||
+ | import net.minecraft.server.network.ServerPlayerEntity; | ||
+ | import net.minecraft.text.Text; | ||
+ | |||
+ | import java.util.HashMap; | ||
+ | |||
+ | public class ExampleMod implements ModInitializer { | ||
+ | |||
+ | public static final String MOD_ID = " | ||
+ | |||
+ | @Override | ||
+ | public void onInitialize() { | ||
+ | HashMap< | ||
+ | |||
+ | PlayerBlockBreakEvents.AFTER.register((world, | ||
+ | if (player instanceof ServerPlayerEntity) { | ||
+ | Item item = player.getMainHandStack().getItem(); | ||
+ | |||
+ | Integer wrongToolUsedCount = tools.getOrDefault(item, | ||
+ | wrongToolUsedCount++; | ||
+ | tools.put(item, | ||
+ | |||
+ | player.sendMessage(Text.literal(" | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | In the code, when we detect the player has broken a block, we pull out the '' | ||
+ | |||
+ | * Note: the '' | ||
+ | |||
+ | If you launch | ||
+ | |||
+ | Next, let's create a custom criterion '' | ||
<code java> | <code java> | ||
Line 346: | Line 388: | ||
import net.minecraft.advancement.criterion.AbstractCriterionConditions; | import net.minecraft.advancement.criterion.AbstractCriterionConditions; | ||
import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; | import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; | ||
- | import net.minecraft.predicate.entity.EntityPredicate; | + | import net.minecraft.predicate.entity.LootContextPredicate; |
import net.minecraft.server.network.ServerPlayerEntity; | import net.minecraft.server.network.ServerPlayerEntity; | ||
- | import net.minecraft.util.Identifier; | ||
- | public class JumpingJacks extends AbstractCriterion< | + | import java.util.Optional; |
+ | import java.util.function.Predicate; | ||
- | /** | + | public class WrongToolCriterion { |
- | /* Don't forget to change: " | + | // |
- | **/ | + | // Nothing yet |
- | | + | |
+ | } | ||
+ | </ | ||
- | @Override | + | Now, write a class inside our '' |
- | | + | |
- | | + | <code java> |
+ | public class WrongToolCriterion { | ||
+ | |||
+ | | ||
+ | public Conditions() { | ||
+ | // The base class Constructor wants an ' | ||
+ | // Since it's optional, we give them nothing. | ||
+ | super(Optional.empty()); | ||
+ | | ||
+ | |||
+ | boolean requirementsMet() { | ||
+ | return true; | ||
+ | } | ||
} | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | * Note: At the moment, our '' | ||
+ | |||
+ | Make our class '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | public class WrongToolCriterion extends AbstractCriterion< | ||
+ | |||
+ | public static class Conditions extends AbstractCriterionConditions { | ||
+ | public Conditions() { | ||
+ | super(Optional.empty()); | ||
+ | } | ||
+ | |||
+ | boolean requirementsMet() { | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The code will now be complaining that you need to implement the '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | public class WrongToolCriterion extends AbstractCriterion< | ||
@Override | @Override | ||
- | | + | |
- | return | + | Optional< |
+ | AdvancementEntityPredicateDeserializer predicateDeserializer) { | ||
+ | Conditions conditions = new Conditions(); | ||
+ | return | ||
} | } | ||
- | public | + | public |
- | | + | public Conditions() { |
+ | super(Optional.empty()); | ||
+ | | ||
+ | |||
+ | boolean requirementsMet() { | ||
return true; | return true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | You may be asking yourself, what exactly is this '' | ||
+ | |||
+ | <code javascript [highlight_lines_extra=" | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | ] | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | " | ||
+ | } | ||
+ | }, | ||
+ | |||
+ | // ... (Rest of json) | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | The '' | ||
+ | |||
+ | The final modification we need to complete our '' | ||
+ | |||
+ | <code java> | ||
+ | public class WrongToolCriterion extends AbstractCriterion< | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | |||
+ | protected void trigger(ServerPlayerEntity player) { | ||
+ | trigger(player, | ||
+ | return conditions.requirementsMet(); | ||
}); | }); | ||
} | } | ||
+ | } | ||
+ | </ | ||
- | public static | + | Inside our trigger function, we call another trigger function (the one in the base class '' |
- | | + | ==== What's a predicate? ==== |
- | | + | |
- | } | + | In simple terms, the '' |
+ | |||
+ | What //we// do is call the function '' | ||
+ | |||
+ | The next step is to add our '' | ||
+ | |||
+ | In your class which '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | import net.fabricmc.api.ModInitializer; | ||
+ | import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; | ||
+ | import net.minecraft.advancement.criterion.Criteria; | ||
+ | import net.minecraft.item.Item; | ||
+ | import net.minecraft.server.network.ServerPlayerEntity; | ||
+ | import net.minecraft.text.Text; | ||
+ | import java.util.HashMap; | ||
+ | |||
+ | public | ||
+ | |||
+ | public static final String MOD_ID = " | ||
+ | |||
+ | public static WrongToolCriterion WRONG_TOOl = Criteria.register(MOD_ID + "/ | ||
+ | |||
+ | @Override | ||
+ | public void onInitialize() { | ||
+ | HashMap< | ||
+ | |||
+ | PlayerBlockBreakEvents.AFTER.register((world, | ||
+ | | ||
+ | Item item = player.getMainHandStack().getItem(); | ||
+ | |||
+ | Integer wrongToolUsedCount = tools.getOrDefault(item, 0); | ||
+ | wrongToolUsedCount++; | ||
+ | tools.put(item, wrongToolUsedCount); | ||
+ | |||
+ | WRONG_TOOl.trigger((ServerPlayerEntity) player); | ||
+ | |||
+ | player.sendMessage(Text.literal(" | ||
+ | } | ||
+ | }); | ||
} | } | ||
} | } | ||
</ | </ | ||
- | You'll notice inside the class, there is another class called '' | + | |
- | Let's create an advancement | + | Now, when we detect our custom game mechanic being activated we call the trigger function of the criterion, and if there are any advancements who use that criteria, they should be satisfied and grant you the advancement. |
+ | |||
+ | In our '' | ||
<code java> | <code java> | ||
- | import net.minecraft.advancement.Advancement; | + | import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; |
- | import net.minecraft.advancement.AdvancementFrame; | + | import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; |
+ | import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; | ||
+ | import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider; | ||
+ | import net.minecraft.advancement.*; | ||
+ | import java.util.function.Consumer; | ||
import net.minecraft.item.Items; | import net.minecraft.item.Items; | ||
import net.minecraft.text.Text; | import net.minecraft.text.Text; | ||
import net.minecraft.util.Identifier; | import net.minecraft.util.Identifier; | ||
- | import java.util.function.Consumer; | + | public class ExampleModDataGenerator |
- | + | ||
- | public class Advancements | + | |
@Override | @Override | ||
- | public void accept(Consumer< | + | public void onInitializeDataGenerator(FabricDataGenerator generator) { |
- | | + | FabricDataGenerator.Pack pack = generator.createPack(); |
- | .display( | + | |
- | Items.BLUE_BED, | + | pack.addProvider(AdvancementsProvider:: |
- | Text.literal(" | + | } |
- | Text.literal(" | + | |
- | new Identifier(" | + | static class AdvancementsProvider extends FabricAdvancementProvider { |
- | AdvancementFrame.TASK, | + | protected AdvancementsProvider(FabricDataOutput dataGenerator) { |
- | true, | + | super(dataGenerator); |
- | true, | + | } |
- | false | + | |
- | ) | + | @Override |
- | .criterion(" | + | public void generateAdvancement(Consumer< |
- | .build(consumer, | + | |
+ | .display( | ||
+ | Items.BELL, | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | new Identifier(" | ||
+ | AdvancementFrame.TASK, | ||
+ | true, | ||
+ | true, | ||
+ | false | ||
+ | ) | ||
+ | .criterion(" | ||
+ | .build(consumer, | ||
+ | } | ||
} | } | ||
} | } | ||
</ | </ | ||
- | Because we've changed the advancement generator code, don't forget to generate the data again. | ||
- | <code bash> | + | Remember that after any modification we make to our '' |
+ | |||
+ | <code bash Windows> | ||
+ | gradlew runDatagen | ||
+ | </ | ||
+ | |||
+ | <code bash Linux> | ||
./gradlew runDatagen | ./gradlew runDatagen | ||
</ | </ | ||
- | Before, with the vanilla provided criterions, we would' | + | Or if you have the configuration |
- | <code java> | + | If you launch the game now, when you break a block, you should be granted our custom advacement satisfied from our custom game mechanic, using our custom criterion. |
+ | |||
+ | ===== Conditions with State ===== | ||
+ | |||
+ | With just what we have, we can already manually have any custom requirements for our advancements, | ||
+ | |||
+ | Look at the following: | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
import net.fabricmc.api.ModInitializer; | import net.fabricmc.api.ModInitializer; | ||
- | import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; | + | import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents; |
import net.minecraft.advancement.criterion.Criteria; | import net.minecraft.advancement.criterion.Criteria; | ||
+ | import net.minecraft.item.Item; | ||
+ | import net.minecraft.server.network.ServerPlayerEntity; | ||
+ | import net.minecraft.text.Text; | ||
+ | import java.util.HashMap; | ||
- | public class AdvancementTutorial | + | public class ExampleMod |
- | | + | |
- | /* You ABSOLUTELY must register your custom | + | |
- | /* criterion as done below for each one you create: | + | public static |
- | */ | + | |
- | public static | + | |
@Override | @Override | ||
public void onInitialize() { | public void onInitialize() { | ||
- | | + | |
- | if (checkedPlayerStateAndHesJumpedOneHundredTimes(handler.player)) { | + | |
- | | + | PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> { |
- | | + | if (player instanceof ServerPlayerEntity) { |
- | | + | Item item = player.getMainHandStack().getItem(); |
- | | + | |
- | | + | Integer wrongToolUsedCount = tools.getOrDefault(item, |
+ | | ||
+ | | ||
+ | |||
+ | | ||
+ | WRONG_TOOl.trigger((ServerPlayerEntity) player); | ||
+ | | ||
+ | |||
+ | | ||
} | } | ||
}); | }); | ||
Line 452: | Line 662: | ||
</ | </ | ||
- | * Because we aren't implementing a full blown mod we pretend a function | + | If you run the game now, and break some blocks, you'll notice the advancement will only be granted when you've used the wrong tool to break some block atleast five times. |
- | * **NOTE:** You must call '' | + | This is really all most modders need. |
- | If you run your game now (changing that fake function to a simple '' | + | So when do you need criterion's that hold state? |
- | The last thing to do is to make a criterion that takes in some data when created | + | You should use them when you have some advancements which are very similar in function but slightly different. Like if we wanted an advancement when the player has used the wrong tool 1 time, and also 5 times, and also 10 times, then what we would do without criterions with state is have to copy and paste our criterion three times blowing up the size of the code, registering all three, and calling them seperately. This is a perfect example of where if the '' |
- | ===== Criterion with State ===== | + | To do so, first, in our '' |
- | We can imagine a mod that has different elemental wands you could use, like fire, water, ice and so on, and that each of these categories has multiple varieties | + | <code java [highlight_lines_extra=" |
+ | public class WrongToolCriterion extends AbstractCriterion< | ||
+ | // ... (Rest of the code) | ||
+ | |||
+ | public static class Conditions extends AbstractCriterionConditions { | ||
+ | |||
+ | Integer minimumAmount; | ||
+ | |||
+ | public Conditions(Integer minimumAmount) { | ||
+ | super(Optional.empty()); | ||
+ | |||
+ | this.minimumAmount = minimumAmount; | ||
+ | } | ||
+ | |||
+ | boolean requirementsMet() { | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | * Note: anywhere | ||
+ | |||
+ | @Override | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | public class WrongToolCriterion extends AbstractCriterion< | ||
+ | // ... (Rest of the code) | ||
+ | |||
+ | public static class Conditions extends AbstractCriterionConditions { | ||
+ | | ||
+ | |||
+ | @Override | ||
+ | public JsonObject toJson() { | ||
+ | JsonObject json = super.toJson(); | ||
+ | json.addProperty(" | ||
+ | return json; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | When our gradle task '' | ||
+ | |||
+ | Rewrite the '' | ||
<code java> | <code java> | ||
- | import com.google.gson.JsonElement; | + | public class WrongToolCriterion extends AbstractCriterion< |
+ | |||
+ | @Override | ||
+ | protected Conditions conditionsFromJson(JsonObject json, | ||
+ | Optional< | ||
+ | AdvancementEntityPredicateDeserializer predicateDeserializer) { | ||
+ | Integer minimiumAmount = json.get(" | ||
+ | Conditions conditions = new Conditions(minimiumAmount); | ||
+ | return conditions; | ||
+ | } | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | As we spoke about before, this funciton is called when the game client is ran and it passes us the '' | ||
+ | |||
+ | Let's use that field now. Add a new parameter to our '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
import com.google.gson.JsonObject; | import com.google.gson.JsonObject; | ||
- | import com.google.gson.JsonPrimitive; | ||
import net.minecraft.advancement.criterion.AbstractCriterion; | import net.minecraft.advancement.criterion.AbstractCriterion; | ||
import net.minecraft.advancement.criterion.AbstractCriterionConditions; | import net.minecraft.advancement.criterion.AbstractCriterionConditions; | ||
import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; | import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; | ||
- | import net.minecraft.predicate.entity.AdvancementEntityPredicateSerializer; | + | import net.minecraft.predicate.entity.LootContextPredicate; |
- | import net.minecraft.predicate.entity.EntityPredicate; | + | |
import net.minecraft.server.network.ServerPlayerEntity; | import net.minecraft.server.network.ServerPlayerEntity; | ||
- | import | + | import |
- | + | ||
- | public class WandCategoryUsed extends AbstractCriterion< | + | |
- | | + | public |
@Override | @Override | ||
- | protected | + | protected |
- | | + | Optional< |
- | | + | |
+ | | ||
+ | | ||
+ | return conditions; | ||
} | } | ||
- | | + | public |
- | | + | |
- | return ID; | + | |
- | } | + | |
- | public void trigger(ServerPlayerEntity player, String wandElement) { | + | Integer minimumAmount; |
- | trigger(player, | + | |
- | } | + | |
- | | + | |
- | | + | |
- | public Condition(String wandElement) { | + | |
- | super(ID, EntityPredicate.Extended.EMPTY); | + | |
- | | + | |
} | } | ||
- | | + | boolean |
- | return | + | return |
} | } | ||
@Override | @Override | ||
- | public JsonObject toJson(AdvancementEntityPredicateSerializer predicateSerializer) { | + | public JsonObject toJson() { |
- | JsonObject | + | JsonObject |
- | | + | |
- | return | + | return |
} | } | ||
+ | } | ||
+ | |||
+ | protected void trigger(ServerPlayerEntity player, Integer amount) { | ||
+ | trigger(player, | ||
+ | return conditions.requirementsMet(amount); | ||
+ | }); | ||
} | } | ||
} | } | ||
</ | </ | ||
- | Our '' | + | In our class which '' |
- | You'll also notice that the '' | + | <code java [highlight_lines_extra=" |
+ | public | ||
- | Now we could write our advancement like so: | + | public static final String MOD_ID = " |
- | <code java> | + | public static WrongToolCriterion WRONG_TOOl = Criteria.register(MOD_ID + "/ |
- | Advancement.Builder.create() | + | |
- | .display(Items.FIRE_WAND_1, Text.literal("Used all water wands"), | + | @Override |
- | | + | public void onInitialize() { |
- | null, | + | HashMap< |
- | | + | |
- | | + | |
- | | + | if (player instanceof ServerPlayerEntity) { |
- | false | + | |
- | | + | |
- | .parent(parentAdvancement) | + | |
- | .criterion(" | + | |
- | .build(consumer, | + | |
+ | |||
+ | WRONG_TOOl.trigger((ServerPlayerEntity) player, wrongToolUsedCount); | ||
+ | |||
+ | player.sendMessage(Text.literal("You've used '" | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | And finally, pass 3 to the custom advancement and create a second one as well. (Dont forget to re-run the gradle task '' | ||
- | Advancement.Builder.create() | + | <code java [highlight_lines_extra=" |
- | .display(Items.ICE_WAND_1, Text.literal(" | + | AdvancementEntry rootAdvancement = Advancement.Builder.createUntelemetered() |
- | Text.literal(" | + | .display( |
- | | + | |
+ | | ||
+ | Text.literal(" | ||
+ | | ||
AdvancementFrame.TASK, | AdvancementFrame.TASK, | ||
true, | true, | ||
Line 546: | Line 838: | ||
false | false | ||
) | ) | ||
- | | + | .criterion(" |
- | | + | .build(consumer, |
- | .build(consumer, | + | |
- | Advancement.Builder.create() | + | AdvancementEntry second = Advancement.Builder.createUntelemetered().parent(rootAdvancement) |
- | .display(Items.WATER_WAND_1, Text.literal(" | + | .display( |
- | Text.literal(" | + | |
- | | + | |
+ | Text.literal(" | ||
+ | | ||
AdvancementFrame.TASK, | AdvancementFrame.TASK, | ||
true, | true, | ||
Line 559: | Line 852: | ||
false | false | ||
) | ) | ||
- | | + | .criterion(" |
- | | + | .build(consumer, |
- | .build(consumer, | + | |
</ | </ | ||
- | Then we'd make sure to register | + | If you run the game now (don't forget |
- | <code java> | + | You can also see the conditions section of the '' |
- | public static WandCategoryUsed WAND_USED = Criteria.register(new WandCategoryUsed()); | + | |
- | </ | + | |
- | And whenever we detected the player having used all the wands for a particular category, we could trigger the advancement like this: | + | < |
- | + | { | |
- | < | + | " |
- | if (playerUsedAllFireWands) | + | "wrong_tool": { |
- | | + | " |
- | } | + | " |
- | if (playerUsedAllWaterWands) | + | }, |
- | | + | |
- | } | + | } |
- | if (playerUsedAllIceWands) { | + | }, |
- | | + | // ... (Rest of JSON) |
} | } | ||
</ | </ |
tutorial/datagen_advancements.1696010929.txt.gz · Last modified: 2023/09/29 18:08 by jmanc3