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/24 00:50] – Made explicit where the criterion needs to be registered. jmanc3 | tutorial:datagen_advancements [2023/10/02 23:11] (current) – Updated custom criterion section jmanc3 | ||
---|---|---|---|
Line 5: | Line 5: | ||
==== Before continuing ==== | ==== 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 '' | + | 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 ===== | ===== Hooking Up the Provider ===== | ||
- | To begin making custom advancements, | + | To begin making custom advancements, |
- | Unfortunately there' | + | <code java> |
- | + | ||
- | <code java [highlight_lines_extra=" | + | |
import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; | import net.fabricmc.fabric.api.datagen.v1.DataGeneratorEntrypoint; | ||
import net.fabricmc.fabric.api.datagen.v1.FabricDataGenerator; | 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; | ||
- | public class DataGeneration | + | public class ExampleModDataGenerator |
@Override | @Override | ||
public void onInitializeDataGenerator(FabricDataGenerator generator) { | public void onInitializeDataGenerator(FabricDataGenerator generator) { | ||
- | | + | |
- | /* Add our advancements generator | + | |
- | **/ | + | |
- | | + | |
- | | + | |
} | } | ||
- | } | ||
- | </ | ||
- | * It should be noted that the '' | + | static |
+ | protected AdvancementsProvider(FabricDataOutput dataGenerator) { | ||
+ | super(dataGenerator); | ||
+ | } | ||
- | We passed the '' | + | |
- | + | public void generateAdvancement(Consumer< | |
- | <code java [highlight_lines_extra=" | + | // |
- | import net.fabricmc.fabric.api.datagen.v1.FabricDataOutput; | + | // We will create our custom advancements here... |
- | import net.fabricmc.fabric.api.datagen.v1.provider.FabricAdvancementProvider; | + | // |
- | import net.minecraft.advancement.Advancement; | + | |
- | + | ||
- | import java.util.function.Consumer; | + | |
- | + | ||
- | public class AdvancementsProvider extends FabricAdvancementProvider { | + | |
- | protected AdvancementsProvider(FabricDataOutput dataGenerator) { | + | |
- | super(dataGenerator); | + | |
- | } | + | |
- | + | ||
- | | + | |
- | public void generateAdvancement(Consumer< | + | |
- | | + | |
} | } | ||
} | } | ||
</ | </ | ||
- | You'll once again notice on the highlighted line that we have another class we haven' | + | * It should be noted that the '' |
- | + | ||
- | Here's how the '' | + | |
- | + | ||
- | <code java> | + | |
- | import net.minecraft.advancement.Advancement; | + | |
- | + | ||
- | import java.util.function.Consumer; | + | |
- | + | ||
- | public class Advancements implements Consumer< | + | |
- | + | ||
- | @Override | + | |
- | public void accept(Consumer< | + | |
- | // | + | |
- | // We will create our custom advancements here... | + | |
- | // | + | |
- | } | + | |
- | } | + | |
- | </ | + | |
===== Simple Advancement ===== | ===== Simple Advancement ===== | ||
- | 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 our '' | + | 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> | <code java> | ||
- | import net.minecraft.advancement.Advancement; | + | // ... (Previous imports) |
- | import net.minecraft.advancement.AdvancementFrame; | + | |
import net.minecraft.advancement.criterion.InventoryChangedCriterion; | import net.minecraft.advancement.criterion.InventoryChangedCriterion; | ||
import net.minecraft.item.Items; | import net.minecraft.item.Items; | ||
Line 86: | Line 56: | ||
import net.minecraft.util.Identifier; | import net.minecraft.util.Identifier; | ||
- | import java.util.function.Consumer; | + | public class ExampleModDataGenerator implements DataGeneratorEntrypoint { |
- | public class Advancements implements Consumer< | + | // ... (Rest of the code) |
- | @Override | + | |
- | public void accept(Consumer< | + | |
- | | + | // ... (Rest of the code) |
- | .display( | + | |
- | Items.DIRT, // The display icon | + | |
- | Text.literal(" | + | public void generateAdvancement(Consumer< |
- | Text.literal(" | + | |
- | new Identifier(" | + | .display( |
- | AdvancementFrame.TASK, | + | Items.DIRT, // The display icon |
- | true, // Show toast top right | + | Text.literal(" |
- | true, // Announce to chat | + | Text.literal(" |
- | false // Hidden in the advancement tab | + | new Identifier(" |
- | ) | + | AdvancementFrame.TASK, |
- | // The first string used in criterion is the name referenced by other advancements when they want to have ' | + | true, // Show toast top right |
- | .criterion(" | + | true, // Announce to chat |
- | .build(consumer, | + | 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, | ||
+ | } | ||
} | } | ||
} | } | ||
</ | </ | ||
- | * Make sure you change the '' | + | * Make sure you change the '' |
- | 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' | + | 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' |
- | So open up your projects root directory | + | If you have a configuration on '' |
+ | or you can open your projects root folder | ||
+ | |||
+ | <code bash Windows> | ||
+ | gradlew runDatagen | ||
+ | </ | ||
- | <code bash> | + | <code bash Linux> |
- | ./ | + | ./ |
</ | </ | ||
- | In the '' | + | In the '' |
<code javascript> | <code javascript> | ||
Line 162: | Line 142: | ||
</ | </ | ||
| | ||
- | 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 '' | + | 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). | * **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). | ||
Line 168: | Line 148: | ||
===== Advancements Explained ===== | ===== Advancements Explained ===== | ||
- | All advancements in minecraft look like that '' | + | 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 '' | + | 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> | ||
- | Advancement | + | AdvancementEntry |
</ | </ | ||
Line 204: | Line 184: | ||
</ | </ | ||
- | 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 '' | + | 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> | <code java> | ||
Line 210: | Line 190: | ||
</ | </ | ||
- | The first argument is a name of type '' | + | The first argument is a name of type '' |
- | * This name is only ever used by '' | + | * This name is only ever used by '' |
- | The second argument is the criterion. In our example we use the '' | + | The second argument is the criterion. In our example we use the '' |
| PlayerHurtEntityCriterion.class | ImpossibleCriterion.class | Criterion.class | AbstractCriterion.class | VillagerTradeCriterion.class | | PlayerHurtEntityCriterion.class | ImpossibleCriterion.class | Criterion.class | AbstractCriterion.class | VillagerTradeCriterion.class | ||
Line 234: | Line 214: | ||
</ | </ | ||
- | We pass it the '' | + | We pass it the '' |
- | * Make sure you change the '' | + | * Make sure you change the '' |
===== One More Example ===== | ===== One More Example ===== | ||
Line 244: | Line 224: | ||
<code java> | <code java> | ||
- | import net.minecraft.advancement.Advancement; | + | package com.example; |
- | import net.minecraft.advancement.AdvancementFrame; | + | |
+ | 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.advancement.criterion.InventoryChangedCriterion; | ||
import net.minecraft.item.Items; | import net.minecraft.item.Items; | ||
Line 251: | Line 238: | ||
import net.minecraft.util.Identifier; | import net.minecraft.util.Identifier; | ||
- | import java.util.function.Consumer; | + | public class ExampleModDataGenerator implements DataGeneratorEntrypoint { |
- | public class Advancements implements Consumer< | + | // ... (Rest of the code) |
- | | + | |
- | public void accept(Consumer< | + | |
- | Advancement 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, | + | |
- | | + | // ... (Rest of the code) |
- | .display( | + | |
- | Items.OAK_LOG, | + | |
- | Text.literal(" | + | |
- | Text.literal(" | + | |
- | null, // children to parent advancements don't need a background set | + | |
- | AdvancementFrame.TASK, | + | |
- | true, | + | |
- | true, | + | |
- | false | + | |
- | ) | + | |
- | | + | |
- | .criterion(" | + | |
- | .build(consumer, | + | |
- | Advancement eatAppleAdvancement = Advancement.Builder.create().parent(rootAdvancement) | + | |
- | .display( | + | public void generateAdvancement(Consumer< |
- | Items.APPLE, | + | AdvancementEntry rootAdvancement = Advancement.Builder.create() |
- | Text.literal(" | + | .display( |
- | Text.literal(" | + | Items.DIRT, // The display icon |
- | null, // children to parent advancements don't need a background set | + | Text.literal(" |
- | AdvancementFrame.CHALLENGE, | + | Text.literal(" |
- | true, | + | new Identifier(" |
- | true, | + | AdvancementFrame.TASK, |
- | false | + | true, // Show toast top right |
- | ) | + | true, // Announce to chat |
- | .criterion(" | + | false // Hidden in the advancement tab |
- | .criterion(" | + | ) |
- | .build(consumer, | + | // 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 | ||
+ | .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, | ||
+ | } | ||
} | } | ||
} | } | ||
</ | </ | ||
- | Don't forget to generate the data. | + | Don't forget to generate the data (Run the gradle task). |
+ | |||
+ | <code bash Windows> | ||
+ | gradlew runDatagen | ||
+ | </ | ||
- | <code bash> | + | <code bash Linux> |
- | ./ | + | ./ |
</ | </ | ||
- | We added another | + | We added an advancement that activates when you get an oak log, and which awards one-thousand experience when completed. And we added another |
<code java> | <code java> | ||
Line 324: | Line 320: | ||
<code java> | <code java> | ||
- | Advancement | + | AdvancementEntry |
// .... | // .... | ||
- | Advancement | + | AdvancementEntry |
</ | </ | ||
* If an advancement doesn' | * If an advancement doesn' | ||
- | We also, of course, changed the titles and descriptions, | + | We also, of course, changed the titles and descriptions, |
===== When To Make a Custom Criterion? ===== | ===== When To Make a Custom Criterion? ===== | ||
Line 343: | 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 350: | 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 | + | * Note: the first parameter |
- | 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 '' |
- | ./gradlew | + | |
+ | <code bash Windows> | ||
+ | gradlew | ||
</ | </ | ||
- | Before, with the vanilla provided criterions, we would' | + | <code bash Linux> |
+ | ./gradlew runDatagen | ||
+ | </ | ||
- | <code java> | + | 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.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 ExampleMod implements ModInitializer { | ||
- | public | + | |
- | | + | public static |
- | /* You ABSOLUTELY must register your custom | + | |
- | /* criterion as done below for each one you create: | + | |
- | */ | + | |
- | | + | |
@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 456: | 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 | + | public class WrongToolCriterion |
- | + | ||
- | public static final Identifier ID = new Identifier(" | + | |
@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 + "/wrong_tool", new WrongToolCriterion()); |
- | Advancement.Builder.create() | + | |
- | .display(Items.FIRE_WAND_1, | + | |
- | Text.literal(" | + | |
- | null, | + | |
- | AdvancementFrame.TASK, | + | |
- | true, | + | |
- | true, | + | |
- | false | + | |
- | ) | + | |
- | .parent(parentAdvancement) | + | |
- | .criterion(" | + | |
- | .build(consumer, | + | |
- | Advancement.Builder.create() | + | @Override |
- | .display(Items.ICE_WAND_1, Text.literal(" | + | public void onInitialize() { |
- | Text.literal(" | + | 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( | ||
+ | | ||
+ | | ||
+ | Text.literal(" | ||
+ | | ||
AdvancementFrame.TASK, | AdvancementFrame.TASK, | ||
true, | true, | ||
Line 550: | 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 563: | 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.1671843022.txt.gz · Last modified: 2022/12/24 00:50 by jmanc3