tutorial:datagen_advancements
Differences
This shows you the differences between two versions of the page.
Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
tutorial:datagen_advancements [2022/12/21 07:01] – jmanc3 | tutorial:datagen_advancements [2023/10/02 23:11] (current) – Updated custom criterion section jmanc3 | ||
---|---|---|---|
Line 1: | Line 1: | ||
====== Advancements Generation ====== | ====== Advancements Generation ====== | ||
- | Before reading this, make sure you've read [[datagen_setup|Getting started with Data Generation]], | + | One way to make a mod feel more integrated into Minecraft is for it to generate custom advancements. How do we do that? |
- | To begin, create | + | ==== Before continuing ==== |
+ | |||
+ | Make sure you've to read the first section of the [[datagen_setup|Getting started with Data Generation]] page, have a class that implements | ||
+ | |||
+ | ===== Hooking Up the Provider ===== | ||
+ | |||
+ | To begin making custom advancements, | ||
<code java> | <code java> | ||
- | private | + | import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; |
- | + | import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; | |
- | protected | + | 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; |
- | | + | |
- | | + | public class ExampleModDataGenerator implements DataGeneratorEntrypoint { |
- | | + | |
- | | + | @Override |
+ | public void onInitializeDataGenerator(FabricDataGenerator generator) { | ||
+ | FabricDataGenerator.Pack pack = generator.createPack(); | ||
+ | |||
+ | pack.addProvider(AdvancementsProvider:: | ||
+ | } | ||
+ | |||
+ | | ||
+ | protected | ||
+ | super(dataGenerator); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void generateAdvancement(Consumer< | ||
+ | // | ||
+ | // We will create our custom advancements here... | ||
+ | // | ||
+ | } | ||
+ | | ||
} | } | ||
+ | </ | ||
- | // ... | + | * It should be noted that the '' |
- | @Override | + | ===== Simple Advancement ===== |
- | public void onInitializeDataGenerator(FabricDataGenerator fabricDataGenerator) { | + | |
- | // ... | + | Let's start simple and work our way up to custom criterions. We'll start with an advancement that activates after you pick up your first dirt block, and we're going to add it to the function '' |
- | | + | |
- | // ... | + | <code java> |
+ | // ... (Previous imports) | ||
+ | import net.minecraft.advancement.criterion.InventoryChangedCriterion; | ||
+ | import net.minecraft.item.Items; | ||
+ | import net.minecraft.text.Text; | ||
+ | import net.minecraft.util.Identifier; | ||
+ | |||
+ | public class ExampleModDataGenerator implements DataGeneratorEntrypoint { | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | |||
+ | static class AdvancementsProvider extends FabricAdvancementProvider { | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | |||
+ | | ||
+ | public void generateAdvancement(Consumer< | ||
+ | | ||
+ | .display( | ||
+ | Items.DIRT, | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | new Identifier(" | ||
+ | | ||
+ | true, // Show toast top right | ||
+ | true, // Announce to chat | ||
+ | false // Hidden in the advancement tab | ||
+ | | ||
+ | // The first string used in criterion is the name referenced by other advancements when they want to have ' | ||
+ | | ||
+ | .build(consumer, | ||
+ | } | ||
+ | } | ||
} | } | ||
</ | </ | ||
- | ==== Advancements ==== | + | * Make sure you change the '' |
- | A quick summary, to create an advancement, | + | |
- | The recommended way to make advancements by the type of advancements | + | I'll explain in more detail what everything means, but if you compile your program now, and jump into a world in minecraft, you'll notice nothing happens. That's because we haven' |
- | **CAUTION**: Before reading more of this, we recommend to read the [[tutorial:blocks|Block]] tutorial first. | + | If you have a configuration on '' |
+ | or you can open your projects root folder on the terminal and run: | ||
- | For this example, we are going to trigger our custom advancement when the user mines our example block. | + | <code bash Windows> |
+ | gradlew runDatagen | ||
+ | </ | ||
- | ==== ADVANCEMENT GENERATOR | + | <code bash Linux> |
- | first, create a generator class like so: | + | ./gradlew runDatagen |
+ | </ | ||
+ | |||
+ | In the '' | ||
+ | |||
+ | <code javascript> | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | ] | ||
+ | } | ||
+ | ] | ||
+ | }, | ||
+ | " | ||
+ | } | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | } | ||
+ | }, | ||
+ | " | ||
+ | [ | ||
+ | " | ||
+ | ] | ||
+ | ] | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | Go ahead and run the game now and see if the advancement works by collecting a dirt block. You should even be able to leave the world, come back, collect another dirt block and notice that there is no re-trigger. If you press '' | ||
+ | |||
+ | * **NOTE:** You have to complete one advancement in the tab group to open it up, otherwise the tab wont show (just in case you were wondering were the vanilla advancements were). | ||
+ | |||
+ | ===== Advancements Explained ===== | ||
+ | |||
+ | All advancements in minecraft look like that '' | ||
+ | |||
+ | Let's go through the advancement we created step by step and see the options we have. We start by calling the '' | ||
<code java> | <code java> | ||
- | private static | + | AdvancementEntry rootAdvancement = Advancement.Builder.create() |
- | | + | </ |
+ | |||
+ | Then we call the chain method ' | ||
+ | |||
+ | <code java> | ||
+ | .display( | ||
+ | /** This is the item that gets used as the icon (You can use any of your mods icons as long as they' | ||
+ | Items.DIRT, | ||
+ | |||
+ | /** This is the text that gets used as the title */ | ||
+ | Text.literal(" | ||
+ | /** This is the text that gets used as the description */ | ||
+ | Text.literal(" | ||
+ | |||
+ | /** This is the background image that is going to be used for the tab in the advancements page. */ | ||
+ | new Identifier(" | ||
+ | |||
+ | /** The type of advancement it should be. */ | ||
+ | AdvancementFrame.TASK, | ||
+ | |||
+ | /** Boolean if when you get the advancement, | ||
+ | true, | ||
+ | /** Boolean if when you get the advancement, | ||
+ | true, | ||
+ | |||
+ | /** Boolean if the advancement should be seen in the advancements page. */ | ||
+ | false | ||
+ | ) | ||
+ | </ | ||
+ | |||
+ | Then we tell Minecraft when this advancement should be triggered (like after eating an item, or in our case, after a block enters our inventory) calling the '' | ||
+ | |||
+ | <code java> | ||
+ | .criterion(" | ||
+ | </ | ||
+ | |||
+ | The first argument is a name of type '' | ||
+ | |||
+ | * This name is only ever used by '' | ||
+ | |||
+ | The second argument is the criterion. In our example we use the '' | ||
+ | |||
+ | | PlayerHurtEntityCriterion.class | ImpossibleCriterion.class | Criterion.class | AbstractCriterion.class | VillagerTradeCriterion.class | ||
+ | | PlayerInteractedWithEntityCriterion.class | InventoryChangedCriterion.class | CriterionConditions.class | AbstractCriterionConditions.class | ||
+ | | RecipeUnlockedCriterion.class | ItemCriterion.class | CriterionProgress.class | BeeNestDestroyedCriterion.class | ||
+ | | ShotCrossbowCriterion.class | ItemDurabilityChangedCriterion.class | CuredZombieVillagerCriterion.class | BredAnimalsCriterion.class | ||
+ | | SlideDownBlockCriterion.class | KilledByCrossbowCriterion.class | EffectsChangedCriterion.class | BrewedPotionCriterion.class | ||
+ | | StartedRidingCriterion.class | LevitationCriterion.class | EnchantedItemCriterion.class | ChangedDimensionCriterion.class | ||
+ | | SummonedEntityCriterion.class | LightningStrikeCriterion.class | EnterBlockCriterion.class | ChanneledLightningCriterion.class | ||
+ | | TameAnimalCriterion.class | OnKilledCriterion.class | EntityHurtPlayerCriterion.class | ConstructBeaconCriterion.class | ||
+ | | TargetHitCriterion.class | PlacedBlockCriterion.class | FilledBucketCriterion.class | ConsumeItemCriterion.class | ||
+ | | ThrownItemPickedUpByEntityCriterion.class | PlayerGeneratesContainerLootCriterion.class | FishingRodHookedCriterion.class | Criteria.class | ||
+ | | TickCriterion.class | TravelCriterion.class | UsedEnderEyeCriterion.class | UsedTotemCriterion.class | UsingItemCriterion.class | ||
+ | |||
+ | And then, the last call to our custom advancement was: | ||
+ | |||
+ | <code java> | ||
+ | .build(consumer, | ||
+ | </ | ||
+ | |||
+ | We pass it the '' | ||
+ | |||
+ | * Make sure you change the '' | ||
+ | |||
+ | ===== One More Example ===== | ||
+ | |||
+ | Just to get the hang of it, lets add two more advancements to our example. | ||
+ | |||
+ | |||
+ | <code java> | ||
+ | package com.example; | ||
+ | |||
+ | import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; | ||
+ | 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.advancement.criterion.ConsumeItemCriterion; | ||
+ | import net.minecraft.advancement.criterion.InventoryChangedCriterion; | ||
+ | import net.minecraft.item.Items; | ||
+ | import net.minecraft.text.Text; | ||
+ | import net.minecraft.util.Identifier; | ||
+ | |||
+ | public class ExampleModDataGenerator | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | |||
+ | static class AdvancementsProvider extends FabricAdvancementProvider { | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | |||
+ | @Override | ||
+ | public void generateAdvancement(Consumer< | ||
+ | AdvancementEntry rootAdvancement = Advancement.Builder.create() | ||
+ | .display( | ||
+ | Items.DIRT, // The display icon | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | new Identifier(" | ||
+ | AdvancementFrame.TASK, | ||
+ | true, // Show toast top right | ||
+ | true, // Announce to chat | ||
+ | false // Hidden in the advancement tab | ||
+ | ) | ||
+ | // The first string used in criterion is the name referenced by other advancements when they want to have ' | ||
+ | .criterion(" | ||
+ | .build(consumer, | ||
+ | |||
+ | AdvancementEntry gotOakAdvancement = Advancement.Builder.create().parent(rootAdvancement) | ||
+ | .display( | ||
+ | Items.OAK_LOG, | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | null, // children to parent advancements don't need a background set | ||
+ | AdvancementFrame.TASK, | ||
+ | true, | ||
+ | true, | ||
+ | false | ||
+ | ) | ||
+ | .rewards(AdvancementRewards.Builder.experience(1000)) | ||
+ | .criterion(" | ||
+ | .build(consumer, | ||
+ | |||
+ | AdvancementEntry eatAppleAdvancement = Advancement.Builder.create().parent(rootAdvancement) | ||
+ | .display( | ||
+ | Items.APPLE, | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | null, // children to parent advancements don't need a background set | ||
+ | AdvancementFrame.CHALLENGE, | ||
+ | true, | ||
+ | true, | ||
+ | false | ||
+ | ) | ||
+ | .criterion(" | ||
+ | .criterion(" | ||
+ | .build(consumer, | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </code> | ||
+ | |||
+ | Don't forget to generate the data (Run the gradle task). | ||
+ | |||
+ | <code bash Windows> | ||
+ | gradlew runDatagen | ||
+ | </ | ||
+ | |||
+ | <code bash Linux> | ||
+ | ./gradlew runDatagen | ||
+ | </ | ||
+ | |||
+ | We added an advancement that activates when you get an oak log, and which awards one-thousand experience when completed. And we added another advancement which the player must actually complete two criterions for, before being awarded the advancement. One criterion to eat an apple, and one criterion to eat cooked beef. This was done by just chain linking some method calls. We've also, importantly, | ||
+ | |||
+ | <code java> | ||
+ | .build(consumer, | ||
+ | |||
+ | // .... | ||
+ | |||
+ | .build(consumer, | ||
+ | </ | ||
+ | |||
+ | Another **key** part is that the two advancements we created are calling the parent function and passing it our root advancement. | ||
+ | |||
+ | <code java> | ||
+ | AdvancementEntry gotOakAdvancement = Advancement.Builder.create().parent(rootAdvancement) | ||
+ | |||
+ | // .... | ||
+ | |||
+ | AdvancementEntry eatAppleAdvancement = Advancement.Builder.create().parent(rootAdvancement) | ||
+ | </code> | ||
+ | |||
+ | * If an advancement doesn' | ||
+ | |||
+ | We also, of course, changed the titles and descriptions, | ||
+ | |||
+ | ===== When To Make a Custom Criterion? ===== | ||
+ | |||
+ | There are many pre-made criterions to choose from that already probably do what you want, and as long as your custom mod items and blocks are registered, you can go pretty far without the use of any custom criterions. How do you know if you need a custom criterion? | ||
+ | |||
+ | The general rule is, if your mod introduces some new mechanic which Minecraft isn't keeping track of, and you want to have an advancement based on it, then make a criterion for it. For example, if your mod adds jumping-jacks into the game, and you want to have an advancement when a player does one hundred of them, how would minecraft know anything about that? It doesn' | ||
+ | |||
+ | ===== How To Make a Custom Criterion? ===== | ||
+ | |||
+ | 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 | ||
+ | |||
+ | | ||
@Override | @Override | ||
- | public void accept(Consumer< | + | public void onInitialize() { |
- | // This is our advancement | + | |
- | // The difference: | + | |
- | + | PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) | |
- | Advancement parentAdvancement | + | if (player instanceof ServerPlayerEntity) { |
- | .display(Tutorial.EXAMPLE_BLOCK, | + | Item item = player.getMainHandStack().getItem(); |
- | Text.translatable(" | + | |
- | new Identifier(" | + | |
- | AdvancementFrame.TASK, | + | |
- | // whether to show a toast that appears on the top-right corner | + | tools.put(item, wrongToolUsedCount); |
- | false, | + | |
- | // whether to announce to chat | + | |
- | false, | + | } |
- | // whether its hidden in the advancement tab | + | }); |
- | false) | + | |
- | .build(consumer, " | + | |
- | + | ||
- | Advancement exampleBlockAdvancement | + | |
- | .parent(parentAdvancement) | + | |
- | .criterion("inventory_changed", InventoryChangedCriterion.Conditions.items(Items.ACACIA_BOAT)) | + | |
- | .build(consumer, | + | |
} | } | ||
} | } | ||
</ | </ | ||
- | Now that we created our advancement in our generator, we register it in our advancements: | + | In the code, when we detect the player has broken a block, we pull out the '' |
+ | |||
+ | * Note: the '' | ||
+ | |||
+ | If you launch the game now, you should see a message pop up everytime you break a block telling you how many times that '' | ||
+ | |||
+ | Next, let's create a custom criterion '' | ||
<code java> | <code java> | ||
- | private static class MyAdvancementGenerators extends FabricAdvancementProvider { | + | import com.google.gson.JsonObject; |
- | private final List< | + | import net.minecraft.advancement.criterion.AbstractCriterion; |
- | list.add(new CustomAdvancementsGenerator()); | + | import net.minecraft.advancement.criterion.AbstractCriterionConditions; |
- | }); | + | import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; |
- | | + | import net.minecraft.predicate.entity.LootContextPredicate; |
- | protected MyAdvancementGenerators(FabricDataGenerator dataGenerator) { | + | import net.minecraft.server.network.ServerPlayerEntity; |
- | | + | |
- | } | + | import java.util.Optional; |
- | | + | import java.util.function.Predicate; |
- | @Override | + | |
- | | + | public |
- | list.forEach(i -> i.accept(consumer)); | + | // |
- | } | + | // Nothing yet |
+ | // | ||
} | } | ||
</ | </ | ||
- | Let' | + | Now, write a class inside our '' |
+ | |||
+ | <code java> | ||
+ | public class WrongToolCriterion { | ||
+ | |||
+ | public static class Conditions extends AbstractCriterionConditions { | ||
+ | public Conditions() { | ||
+ | // The base class Constructor wants an ' | ||
+ | // Since it' | ||
+ | super(Optional.empty()); | ||
+ | } | ||
+ | |||
+ | boolean requirementsMet() { | ||
+ | return true; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | * Note: At the moment, | ||
+ | |||
+ | 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 | ||
+ | protected Conditions conditionsFromJson(JsonObject json, | ||
+ | Optional< | ||
+ | AdvancementEntityPredicateDeserializer predicateDeserializer) { | ||
+ | Conditions conditions = new Conditions(); | ||
+ | return conditions; | ||
+ | } | ||
+ | |||
+ | public static class Conditions extends AbstractCriterionConditions { | ||
+ | public Conditions() { | ||
+ | super(Optional.empty()); | ||
+ | } | ||
+ | |||
+ | boolean requirementsMet() { | ||
+ | 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(); | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | 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 class ExampleMod implements ModInitializer { | ||
+ | |||
+ | public static final String MOD_ID = " | ||
+ | |||
+ | public static WrongToolCriterion WRONG_TOOl = Criteria.register(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, | ||
+ | |||
+ | WRONG_TOOl.trigger((ServerPlayerEntity) player); | ||
+ | |||
+ | player.sendMessage(Text.literal(" | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | * Note: the first parameter (a string) can be anything. We prepend it with the '' | ||
+ | |||
+ | 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> | ||
+ | import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; | ||
+ | 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.text.Text; | ||
+ | import net.minecraft.util.Identifier; | ||
+ | |||
+ | public class ExampleModDataGenerator implements DataGeneratorEntrypoint { | ||
+ | |||
+ | @Override | ||
+ | public void onInitializeDataGenerator(FabricDataGenerator generator) { | ||
+ | FabricDataGenerator.Pack pack = generator.createPack(); | ||
+ | |||
+ | pack.addProvider(AdvancementsProvider:: | ||
+ | } | ||
+ | |||
+ | static class AdvancementsProvider extends FabricAdvancementProvider { | ||
+ | protected AdvancementsProvider(FabricDataOutput dataGenerator) { | ||
+ | super(dataGenerator); | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public void generateAdvancement(Consumer< | ||
+ | AdvancementEntry rootAdvancement = Advancement.Builder.createUntelemetered() | ||
+ | .display( | ||
+ | Items.BELL, | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | new Identifier(" | ||
+ | AdvancementFrame.TASK, | ||
+ | true, | ||
+ | true, | ||
+ | false | ||
+ | ) | ||
+ | .criterion(" | ||
+ | .build(consumer, | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | |||
+ | Remember that after any modification we make to our '' | ||
+ | |||
+ | <code bash Windows> | ||
+ | gradlew runDatagen | ||
+ | </ | ||
+ | |||
+ | <code bash Linux> | ||
+ | ./gradlew runDatagen | ||
+ | </ | ||
+ | |||
+ | Or if you have the configuration in your IDE, run that. | ||
+ | |||
+ | 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.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 class ExampleMod implements ModInitializer { | ||
+ | |||
+ | public static final String MOD_ID = " | ||
+ | |||
+ | public static WrongToolCriterion WRONG_TOOl = Criteria.register(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, | ||
+ | |||
+ | if (wrongToolUsedCount > 5) { | ||
+ | WRONG_TOOl.trigger((ServerPlayerEntity) player); | ||
+ | } | ||
+ | |||
+ | player.sendMessage(Text.literal(" | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | If you run the game now, and break some blocks, you'll notice | ||
+ | |||
+ | This is really all most modders need. | ||
+ | |||
+ | So when do you need criterion' | ||
+ | |||
+ | You should use them when you have some advancements | ||
+ | |||
+ | To do so, first, in our '' | ||
+ | |||
+ | <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 in the code where '' | ||
+ | |||
+ | @Override the '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | public class WrongToolCriterion extends AbstractCriterion< | ||
+ | // ... (Rest of the code) | ||
+ | |||
+ | public static class Conditions extends AbstractCriterionConditions { | ||
+ | // ... (Rest of the code) | ||
+ | |||
+ | @Override | ||
+ | public JsonObject toJson() { | ||
+ | JsonObject json = super.toJson(); | ||
+ | json.addProperty(" | ||
+ | return json; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | // ... (Rest of the code) | ||
+ | } | ||
+ | |||
+ | </ | ||
+ | |||
+ | When our gradle task '' | ||
+ | |||
+ | Rewrite the '' | ||
+ | |||
+ | <code java> | ||
+ | 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 | ||
+ | |||
+ | Let's use that field now. Add a new parameter to our '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | import com.google.gson.JsonObject; | ||
+ | import net.minecraft.advancement.criterion.AbstractCriterion; | ||
+ | import net.minecraft.advancement.criterion.AbstractCriterionConditions; | ||
+ | import net.minecraft.predicate.entity.AdvancementEntityPredicateDeserializer; | ||
+ | import net.minecraft.predicate.entity.LootContextPredicate; | ||
+ | import net.minecraft.server.network.ServerPlayerEntity; | ||
+ | import java.util.Optional; | ||
+ | |||
+ | 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; | ||
+ | } | ||
+ | |||
+ | public static class Conditions extends AbstractCriterionConditions { | ||
+ | |||
+ | Integer minimumAmount; | ||
+ | |||
+ | public Conditions(Integer minimumAmount) { | ||
+ | super(Optional.empty()); | ||
+ | |||
+ | this.minimumAmount = minimumAmount; | ||
+ | } | ||
+ | |||
+ | boolean requirementsMet(Integer amount) { | ||
+ | return amount > minimumAmount; | ||
+ | } | ||
+ | |||
+ | @Override | ||
+ | public JsonObject toJson() { | ||
+ | JsonObject json = super.toJson(); | ||
+ | json.addProperty(" | ||
+ | return json; | ||
+ | } | ||
+ | } | ||
+ | |||
+ | protected void trigger(ServerPlayerEntity player, Integer amount) { | ||
+ | trigger(player, | ||
+ | return conditions.requirementsMet(amount); | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | In our class which '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | public class ExampleMod implements ModInitializer { | ||
+ | |||
+ | public static final String MOD_ID = " | ||
+ | |||
+ | public static WrongToolCriterion WRONG_TOOl = Criteria.register(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, | ||
+ | |||
+ | WRONG_TOOl.trigger((ServerPlayerEntity) player, wrongToolUsedCount); | ||
+ | |||
+ | player.sendMessage(Text.literal(" | ||
+ | } | ||
+ | }); | ||
+ | } | ||
+ | } | ||
+ | </ | ||
+ | |||
+ | And finally, pass 3 to the custom advancement and create a second one as well. (Dont forget to re-run the gradle task '' | ||
+ | |||
+ | <code java [highlight_lines_extra=" | ||
+ | AdvancementEntry rootAdvancement = Advancement.Builder.createUntelemetered() | ||
+ | .display( | ||
+ | Items.BELL, | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | new Identifier(" | ||
+ | AdvancementFrame.TASK, | ||
+ | true, | ||
+ | true, | ||
+ | false | ||
+ | ) | ||
+ | .criterion(" | ||
+ | .build(consumer, | ||
+ | |||
+ | AdvancementEntry second = Advancement.Builder.createUntelemetered().parent(rootAdvancement) | ||
+ | .display( | ||
+ | Items.QUARTZ, | ||
+ | Text.literal(" | ||
+ | Text.literal(" | ||
+ | new Identifier(" | ||
+ | AdvancementFrame.TASK, | ||
+ | true, | ||
+ | true, | ||
+ | false | ||
+ | ) | ||
+ | .criterion(" | ||
+ | .build(consumer, | ||
+ | </ | ||
+ | |||
+ | If you run the game now (don't forget to re-run the gradle task '' | ||
+ | |||
+ | You can also see the conditions section of the '' | ||
+ | |||
+ | <code javascript> | ||
+ | { | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | " | ||
+ | }, | ||
+ | " | ||
+ | } | ||
+ | }, | ||
+ | // ... (Rest of JSON) | ||
+ | } | ||
+ | </ |
tutorial/datagen_advancements.1671606060.txt.gz · Last modified: 2022/12/21 07:01 by jmanc3