User Tools

Site Tools


tutorial:datagen_advancements

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revisionPrevious revision
tutorial:datagen_advancements [2023/09/29 18:08] – Updated pre-custom criterion section for fabric 1.20.2 jmanc3tutorial: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 has done, and we want to make an advancement when they complete one hundredFirst thing we got to do is make the ''**JumpingJacks**'' class which will extend the ''**AbstractCriterion<JumpingJacks.Condition>**'' class like so:+To start, let's create a new minecraft mechanic. 
 + 
 +In your ''**.java**'' class which ''**implements ModInitializer**'', write the following: 
 + 
 +<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 = "your_unique_mod_id_change_me_please"; 
 + 
 +    @Override 
 +    public void onInitialize() { 
 +        HashMap<Item, Integer> tools = new HashMap<>(); 
 + 
 +        PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> { 
 +            if (player instanceof ServerPlayerEntity) { 
 +                Item item = player.getMainHandStack().getItem(); 
 + 
 +                Integer wrongToolUsedCount = tools.getOrDefault(item, 0); 
 +                wrongToolUsedCount++; 
 +                tools.put(item, wrongToolUsedCount); 
 + 
 +                player.sendMessage(Text.literal("You've used '" + item + "' as a wrong tool: " + wrongToolUsedCount + " times.")); 
 +            } 
 +        }); 
 +    } 
 +
 +</code> 
 + 
 +In the code, when we detect the player has broken a block, we pull out the ''**Integer**'' out of the hashmap using the active ''**Item**'' in the players hand and increase the ''**Integer**'' by one. We then store that ''**Integer**'' back in the ''**HashMap**''
 + 
 +  * Note: the ''**HashMap**'' isn't being saved using [[persistent_states|Persistent Storage]] so when the world closes, whatever ''**Integer**'''s were being stored, are lost. The mechanic is also //not// keeping track of each player individually. In other words: This code is really bad, and written solely to show off custom criterions. 
 + 
 +If you launch the game now, you should see a message pop up everytime you break a block telling you how many times that ''**Item**'' has been used as a 'wrong' tool. Understand what is happening thoroughly before continuing. 
 + 
 +Next, let's create a custom criterion ''**WrongToolCriterion**'' which will be triggered and granted when we detect our custom game mechanic. In the same folder as your ''**.java**'' class which ''**implements ModInitializer**'' create a new file ''**WrongToolCriterion.java**'' and fill it as follows:
  
 <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<JumpingJacks.Condition> {+import java.util.Optional; 
 +import java.util.function.Predicate;
  
-    /** +public class WrongToolCriterion { 
-    /* Don't forget to change: "your_mod_id_please_change_me" +    // 
-     **+    // Nothing yet 
-    public static final Identifier ID = new Identifier("your_mod_id_please_change_me", "jumping_jacks");+    // 
 +
 +</code>
  
-    @Override +Now, write a class inside our ''**WrongToolCriterion**'' class which ''**extends AbstractCriterionConditions**''. We will call it ''**Conditions**''. It will force you to implement the constructor. 
-    protected Condition conditionsFromJson(JsonObject obj, EntityPredicate.Extended playerPredicate, AdvancementEntityPredicateDeserializer predicateDeserializer{ + 
-        return new Condition();+<code java> 
 +public class WrongToolCriterion { 
 + 
 +    public static class Conditions extends AbstractCriterionConditions { 
 +        public Conditions() { 
 +            // The base class Constructor wants an 'Optional<LootContextPredicate> playerPredicate'. 
 +            // Since it's optionalwe give them nothing. 
 +            super(Optional.empty()); 
 +        
 + 
 +        boolean requirementsMet() 
 +            return true; 
 +        }
     }     }
 +}
 +</code>
 +
 +  * Note: At the moment, our ''**WrongToolCriterion**'' has no requirements, so we just return true always from the function ''**requirementsMet**''.
 +
 +Make our class ''**WrongToolCriterion**'' extend ''**AbstractCriterion**'' which will take a type. In fact, it's going to take a ''**class**'' of type ''**AbstractCriterionConditions**'' (the class we just wrote). Should look like:
 +
 +<code java [highlight_lines_extra="1"]>
 +public class WrongToolCriterion extends AbstractCriterion<WrongToolCriterion.Conditions> {
 +
 +    public static class Conditions extends AbstractCriterionConditions {
 +        public Conditions() {
 +            super(Optional.empty());
 +        }
 +
 +        boolean requirementsMet() {
 +            return true;
 +        }
 +    }
 +}
 +</code>
 +
 +The code will now be complaining that you need to implement the ''**conditionsFromJson**'' function. So do so:
 +
 +<code java [highlight_lines_extra="3,4,5,6,7,8,9"]>
 +public class WrongToolCriterion extends AbstractCriterion<WrongToolCriterion.Conditions> {
  
     @Override     @Override
-    public Identifier getId() { +    protected Conditions conditionsFromJson(JsonObject json, 
-        return ID;+                                            Optional<LootContextPredicate> playerPredicate, 
 +                                            AdvancementEntityPredicateDeserializer predicateDeserializer) { 
 +        Conditions conditions = new Conditions(); 
 +        return conditions;
     }     }
  
-    public void trigger(ServerPlayerEntity player) { +    public static class Conditions extends AbstractCriterionConditions { 
-        trigger(player, condition -> {+        public Conditions() { 
 +            super(Optional.empty()); 
 +        
 + 
 +        boolean requirementsMet({
             return true;             return true;
 +        }
 +    }
 +}
 +</code>
 +
 +You may be asking yourself, what exactly is this ''**conditionsFromJson**'' function? When does it get called? Who calls it? What data does it get passed in? And what is it supposed to return? All very good questions. As has been mentioned before, advancements are simply ''**.json**'' files. This function ''**conditionsFromJson**'' gets passed in the ''**conditions**'' section from our advancements' ''**.json**'' files. In fact, the example advancements we wrote earlier in the article (''**root.json**'') had a conditions section.
 +
 +<code javascript [highlight_lines_extra="4,5,6,7,8,9,10,11,12"]>
 +{
 +  "criteria": {
 +    "got_dirt": {
 +      "conditions": {
 +        "items": [
 +          {
 +            "items": [
 +              "minecraft:dirt"
 +            ]
 +          }
 +        ]
 +      },
 +      "trigger": "minecraft:inventory_changed"
 +    }
 +  },
 +
 +  // ... (Rest of json)
 +}
 +</code>
 +
 +The ''**JsonObject**'' which you receive in the function is simply the above highlighted json. You are meant to pull out any data you need from it and pass it to the constructor of the ''**Conditions**'' object, and save it as fields. We then return that ''**new Conditions**'' object. Since our custom criterion is not going to have any 'requirements' we won't need to worry about loading and saving from json, yet.
 +
 +The final modification we need to complete our ''**WrongToolCriterion**'' is to write a trigger function. This is the function we are going to call when we detect our new game mechanic activating.
 +
 +<code java>
 +public class WrongToolCriterion extends AbstractCriterion<WrongToolCriterion.Conditions> {
 +
 +    // ... (Rest of the code)
 +
 +    protected void trigger(ServerPlayerEntity player) {
 +        trigger(player, conditions -> {
 +            return conditions.requirementsMet();
         });         });
     }     }
 +}
 +</code>
  
-    public static class Condition extends AbstractCriterionConditions {+Inside our trigger function, we call another trigger function (the one in the base class ''**AbstractCriterion**''). The function which we are calling takes in a ''**ServerPlayerEntity**'' and a ''**Predicate<Conditions>**''.
  
-        public Condition() { +==== What's a predicate? ==== 
-            super(IDEntityPredicate.Extended.EMPTY); + 
-        }+In simple terms, the ''**Predicate**'' is going to hand you a variable of the type which is inside its ''<...>'' (in our case we get handed a variable of the type: ''**Conditions**'' (the class we wrote)) and it wants us to return true or false based on whatever criteria we want. 
 + 
 +What //we// do is call the function ''**requirementsMet**'' (which returns a boolean) on the conditions variable, and return that. (We could've just ''**return true**'' instead of ''**return conditions.requirementsMet()**'' as well.) It will become clear how you can use this to have requirements on your criterions soon, but for now, any time our ''**trigger**'' function gets called, it grants the advacement. 
 + 
 +The next step is to add our ''**WrongToolCriterion**'' to the ''**Criteria**'' register.  
 + 
 +In your class which ''**implements ModInitializer**'' add the following: 
 + 
 +<code java [highlight_lines_extra="13,27"]> 
 +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 = "your_unique_mod_id_change_me_please"; 
 + 
 +    public static WrongToolCriterion WRONG_TOOl = Criteria.register(MOD_ID + "/wrong_tool", new WrongToolCriterion()); 
 + 
 +    @Override 
 +    public void onInitialize() 
 +        HashMap<Item, Integer> tools = new HashMap<>(); 
 + 
 +        PlayerBlockBreakEvents.AFTER.register((world, player, pos, state, entity) -> 
 +            if (player instanceof ServerPlayerEntity) { 
 +                Item item = player.getMainHandStack().getItem(); 
 + 
 +                Integer wrongToolUsedCount = tools.getOrDefault(item0); 
 +                wrongToolUsedCount++; 
 +                tools.put(item, wrongToolUsedCount); 
 + 
 +                WRONG_TOOl.trigger((ServerPlayerEntity) player); 
 + 
 +                player.sendMessage(Text.literal("You've used '" + item + "' as a wrong tool: " + wrongToolUsedCount + " times.")); 
 +            } 
 +        });
     }     }
 } }
 </code> </code>
  
-You'll notice inside the class, there is another class called ''**Condition**'' which implements ''**AbstractCriterionConditions**''. It just calls the super function for now and nothing else. In fact this whole class is basically doing nothing, (other than making an ID). The only function that does anything is the ''**trigger**'' function which calls the ''**trigger**'' function which exists in the ''**AbstractCriterion**'' class we extended, and which we, with no checking against any data, return true always. That means that **any** time this JumpingJacks criterion is triggered, it'll award the player the advancement.+  Note: the first parameter (a stringcan be anythingWe prepend it with the ''**MOD_ID**'' so that our name doesn't clash with any of the default minecraft ''**Criterion**'''s or other mods.
  
-Let's create an advancement with it now.+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 ''**FabricAdvancementProvider**'' class which we wrote earlier write the following advancement using our custom criteria:
  
 <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 implements DataGeneratorEntrypoint {
- +
-public class Advancements implements Consumer<Consumer<Advancement>> {+
  
     @Override     @Override
-    public void accept(Consumer<Advancement> consumer) { +    public void onInitializeDataGenerator(FabricDataGenerator generator) { 
-        Advancement rootAdvancement = Advancement.Builder.create() +        FabricDataGenerator.Pack pack = generator.createPack(); 
-                .display( + 
-                        Items.BLUE_BED+        pack.addProvider(AdvancementsProvider::new); 
-                        Text.literal("Jumping Jacks"), +    } 
-                        Text.literal("You jumped Jack 100 times"), + 
-                        new Identifier("textures/gui/advancements/backgrounds/adventure.png"), +    static class AdvancementsProvider extends FabricAdvancementProvider { 
-                        AdvancementFrame.TASK, +        protected AdvancementsProvider(FabricDataOutput dataGenerator) { 
-                        true, +            super(dataGenerator); 
-                        true, +        } 
-                        false + 
-                +        @Override 
-                .criterion("jumping_jacks", new JumpingJacks.Condition()) +        public void generateAdvancement(Consumer<AdvancementEntry> consumer) { 
-                .build(consumer, "your_mod_id_please_change_me" + "/root");+            AdvancementEntry rootAdvancement = Advancement.Builder.createUntelemetered() 
 +                    .display( 
 +                            Items.BELL
 +                            Text.literal("Wrong Tool Buddy"), 
 +                            Text.literal("That's not the right tool"), 
 +                            new Identifier("textures/gui/advancements/backgrounds/adventure.png"), 
 +                            AdvancementFrame.TASK, 
 +                            true, 
 +                            true, 
 +                            false 
 +                    
 +                    .criterion("wrong_tool", ExampleMod.WRONG_TOOl.create(new WrongToolCriterion.Conditions())) 
 +                    .build(consumer, "your_mod_id_please_change_me" + "/root"); 
 +        }
     }     }
 } }
 </code> </code>
  
-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 ''**DataGeneratorEntrypoint**'' we need to run the gradle task ''**runDatagen**''
 + 
 +<code bash Windows> 
 +gradlew runDatagen 
 +</code> 
 + 
 +<code bash Linux>
 ./gradlew runDatagen ./gradlew runDatagen
 </code> </code>
  
-Before, with the vanilla provided criterions, we would've been done at this stage, but because we created the criterion, we need to actually call the trigger function ourselves, and register it. Behind the scenes, for the consume item criterion for instance, minecraft is doing this kind of trigger. You can see in the eat function that every time the player eatsit sends the item that was ate to the consume item criterion to check if an advancement should be awarded. We have to do the same for our mod. We are responsible for calling the trigger functionHere's an example:+Or if you have the configuration in your IDErun that.
  
-<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, that is, maybe we'd like are advancement to only be granted if the player has used the wrong tool atleast five times, or they had to be jumping while doing it, or any other things we might want to be true. And then, only when all the things we want to be true, are true, do we call the trigger function. 
 + 
 +Look at the following: 
 + 
 +<code java [highlight_lines_extra="27,28,29"]>
 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 implements ModInitializer {+public class ExampleMod implements ModInitializer {
  
-    /** +    public static final String MOD_ID = "your_unique_mod_id_change_me_please"; 
-    /* You ABSOLUTELY must register your custom + 
-    /* criterion as done below for each one you create: +    public static WrongToolCriterion WRONG_TOOl = Criteria.register(MOD_ID + "/wrong_tool", new WrongToolCriterion());
-     */ +
-    public static JumpingJacks JUMPING_JACKS = Criteria.register(new JumpingJacks());+
  
     @Override     @Override
     public void onInitialize() {     public void onInitialize() {
-        ServerPlayConnectionEvents.JOIN.register((handlerorigindestination) -> { +        HashMap<Item, Integer> tools = new HashMap<>(); 
-            if (checkedPlayerStateAndHesJumpedOneHundredTimes(handler.player)) { + 
-                // +        PlayerBlockBreakEvents.AFTER.register((worldplayer, pos, stateentity) -> { 
-                // Because of the way we wrote our JumpingJacks class,  +            if (player instanceof ServerPlayerEntity) { 
-                // calling the trigger function will ALWAYS grant us the advancement+                Item item = player.getMainHandStack().getItem()
-                // + 
-                JUMPING_JACKS.trigger(handler.player);+                Integer wrongToolUsedCount = tools.getOrDefault(item, 0); 
 +                wrongToolUsedCount++; 
 +                tools.put(itemwrongToolUsedCount); 
 + 
 +                if (wrongToolUsedCount > 5) { 
 +                    WRONG_TOOl.trigger((ServerPlayerEntity) player); 
 +                
 + 
 +                player.sendMessage(Text.literal("You've used '" + item + "' as a wrong tool: " + wrongToolUsedCount + " times."));
             }             }
         });         });
Line 452: Line 662:
 </code> </code>
  
-  * Because we aren't implementing a full blown mod we pretend a function ''**checkedPlayerStateAndHesJumpedOneHundredTimes**'' exists and returns true when the player has done more than one-hundred jumping jacks.+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 ''**Criteria.register**'' with your custom made criterion, or your game won't award the advancements. (And it MUST be done server side, which is why we do this in the ''**ModInitializer**'' class).+This is really all most modders need.
  
-If you run your game now (changing that fake function to a simple ''**true**'' so it compiles), when you log into a world, you should be granted the jumping jack advancement, but because we are using the server join event here to do this, it gives it to you before you load in, which is why you don't get a toast message. If you open up the advancements page in the escape menu, you'll see it was in fact granted.+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 and uses it during when the trigger function is called(Like how the consume item criterion takes an ''**Item**'', and then only triggers when that specific ''**Item**'' is consumed).+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 seperatelyThis is a perfect example of where if the ''**Conditions**'' simply took an ''**Integer**'' specifying how many times an item had to be used as a wrong tool before activating, it would greately improve our code.
  
-===== Criterion with State =====+To do so, first, in our ''**Conditions**'' class, add a parameter to the constructor, an ''**Integer**'', and assign it to a field, that way the ''**Conditions**'' has a copy of the number for later use.
  
-We can imagine a mod that has different elemental wands you could uselike fire, water, ice and so on, and that each of these categories has multiple varieties (2 fire wands, 4 water wands, 3 ice wands). Let's say we want to make an advancement every time the player has used every wand in a specific category (all the ice wandsor all the fire wands). We //could// use what we know so far to accomplish thisWe would simply make three criterionsfire, water, and ice, that we trigger when we've detected the user has used all the wands in that categoryBut we could save ourselves a lot of copy pasting by passing in a little bit of state when the criterion is made.+<code java [highlight_lines_extra="6,11"]> 
 +public class WrongToolCriterion extends AbstractCriterion<WrongToolCriterion.Conditions>
 +    // ... (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) 
 +
 + 
 +</code> 
 + 
 +  * Note: anywhere in the code where ''**new WrongToolCriterion.Conditions()**'' was called should be complaining but lets ignore it for now. 
 + 
 +@Override the ''**toJson**'' function inside ''**Conditions**'' and write the following: 
 + 
 +<code java [highlight_lines_extra="7,8,9,10,11,12"]> 
 +public class WrongToolCriterion extends AbstractCriterion<WrongToolCriterion.Conditions>
 +    // ... (Rest of the code) 
 + 
 +    public static class Conditions extends AbstractCriterionConditions { 
 +        // ... (Rest of the code) 
 + 
 +        @Override 
 +        public JsonObject toJson() { 
 +            JsonObject json = super.toJson(); 
 +            json.addProperty("amount", minimumAmount); 
 +            return json; 
 +        } 
 +    } 
 + 
 +    // ... (Rest of the code) 
 +
 + 
 +</code> 
 + 
 +When our gradle task ''**runDatagen**'' is ranit calls //this// ''**toJson**'' function when it's writing the conditions portion of our custom advacementsThat's why we make sure to add to the ''**JsonObject**'' our ''**Conditions**'''s private data: the field ''**minimumAmount**''. That way when the game is ran, and it reads the advacement off the disk (in ''**conditionsFromJson**''), it can read off the ''**Integer**'' we saved here. 
 + 
 +Rewrite the ''**conditionsFromJson**'' as follows:
  
 <code java> <code java>
-import com.google.gson.JsonElement;+public class WrongToolCriterion extends AbstractCriterion<WrongToolCriterion.Conditions>
 + 
 +    @Override 
 +    protected Conditions conditionsFromJson(JsonObject json, 
 +                                            Optional<LootContextPredicate> playerPredicate, 
 +                                            AdvancementEntityPredicateDeserializer predicateDeserializer) { 
 +        Integer minimiumAmount = json.get("amount").getAsInt(); 
 +        Conditions conditions = new Conditions(minimiumAmount); 
 +        return conditions; 
 +    } 
 + 
 +    // ... (Rest of the code) 
 +
 +</code> 
 + 
 +As we spoke about before, this funciton is called when the game client is ran and it passes us the ''**JsonObject**'' we wrote in ''**toJson**'', therefore we read out the ''**amount**'' and cast it to an ''**Integer**'' which we know it is. We then pass that to the ''**Conditions**'' constructor so it can store it as a field. 
 + 
 +Let's use that field now. Add a new parameter to our ''**requirementsMet**'' function, an ''**Integer**'', which is supposed to be the amount that a particular item has used the wrong tool. In the function we will return true if that item has used more than the minimium the criteria requires. The final ''**WrongToolCriterion**'' should be as follows: 
 + 
 +<code java [highlight_lines_extra="30,31,32,42,43,44,45"]>
 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 net.minecraft.util.Identifier; +import java.util.Optional;
- +
-public class WandCategoryUsed extends AbstractCriterion<WandCategoryUsed.Condition> {+
  
-    public static final Identifier ID = new Identifier("your_modid_here_please_change_me", "finished_wand_category");+public class WrongToolCriterion extends AbstractCriterion<WrongToolCriterion.Conditions> {
  
     @Override     @Override
-    protected Condition conditionsFromJson(JsonObject objEntityPredicate.Extended playerPredicate, AdvancementEntityPredicateDeserializer predicateDeserializer) { +    protected Conditions conditionsFromJson(JsonObject json, 
-        JsonElement cravingTarget obj.get("wandElement"); +                                            Optional<LootContextPredicate> playerPredicate, 
-        return new Condition(cravingTarget.getAsString());+                                            AdvancementEntityPredicateDeserializer predicateDeserializer) { 
 +        Integer minimiumAmount json.get("amount").getAsInt(); 
 +        Conditions conditions = new Conditions(minimiumAmount)
 +        return conditions;
     }     }
  
-    @Override +    public static class Conditions extends AbstractCriterionConditions {
-    public Identifier getId() { +
-        return ID; +
-    }+
  
-    public void trigger(ServerPlayerEntity player, String wandElement) { +        Integer minimumAmount;
-        trigger(player, condition -> condition.test(wandElement)); +
-    }+
  
-    public static class Condition extends AbstractCriterionConditions +        public Conditions(Integer minimumAmount) 
-        String wandElement;+            super(Optional.empty());
  
-        public Condition(String wandElement) { +            this.minimumAmount minimumAmount;
-            super(ID, EntityPredicate.Extended.EMPTY); +
-            this.wandElement wandElement;+
         }         }
  
-        public boolean test(String wandElement) { +        boolean requirementsMet(Integer amount) { 
-            return this.wandElement.equals(wandElement);+            return amount > minimumAmount;
         }         }
  
         @Override         @Override
-        public JsonObject toJson(AdvancementEntityPredicateSerializer predicateSerializer) { +        public JsonObject toJson() { 
-            JsonObject jsonObject = super.toJson(predicateSerializer); +            JsonObject json = super.toJson(); 
-            jsonObject.add("wandElement", new JsonPrimitive(wandElement)); +            json.addProperty("amount", minimumAmount); 
-            return jsonObject;+            return json;
         }         }
 +    }
 +
 +    protected void trigger(ServerPlayerEntity player, Integer amount) {
 +        trigger(player, conditions -> {
 +            return conditions.requirementsMet(amount);
 +        });
     }     }
 } }
 </code> </code>
  
-Our ''**Condition**'' class now takes in a string in it's constructor and saves it for later use. The ''**Condition**'' class also has a new function ''**test**'' which takes in a string and returns true if it equals it's own ''**wandElment**'' string. In the ''**toJson**'' function we convert the ''**wandElement**'' to json so it can be saved to disk.+In our class which ''**implements ModInitializer**'' re-write our trigger function:
  
-You'll also notice that the ''**trigger**'' function doesn't just return true now, it actually uses the new ''**test**'' function in the ''**Condition**'' class to see if the passed in data matches. And in the ''**conditionsFromJson**'' we convert the saved out ''**wandElement**'' json back to string.+<code java [highlight_lines_extra="19"]> 
 +public class ExampleMod implements ModInitializer {
  
-Now we could write our advancement like so:+    public static final String MOD_ID = "your_unique_mod_id_change_me_please";
  
-<code java> +    public static WrongToolCriterion WRONG_TOOl = Criteria.register(MOD_ID + "/wrong_tool", new WrongToolCriterion()); 
-Advancement.Builder.create() + 
-        .display(Items.FIRE_WAND_1Text.literal("Used all water wands"), +    @Override 
-                Text.literal("Used all water wands"), +    public void onInitialize() { 
-                null, +        HashMap<Item, Integer> tools = new HashMap<>()
-                AdvancementFrame.TASK+ 
-                true, +        PlayerBlockBreakEvents.AFTER.register((worldplayer, pos, state, entity) -> { 
-                true, +            if (player instanceof ServerPlayerEntity{ 
-                false +                Item item = player.getMainHandStack().getItem(); 
-        + 
-        .parent(parentAdvancement+                Integer wrongToolUsedCount = tools.getOrDefault(item0); 
-        .criterion("used_all_fire_wands", new WandCategoryUsed.Condition("fire")) +                wrongToolUsedCount++; 
-        .build(consumer, "your_mod_id_please_change_me" + "/used_all_fire_wands");+                tools.put(itemwrongToolUsedCount); 
 + 
 +                WRONG_TOOl.trigger((ServerPlayerEntityplayer, wrongToolUsedCount); 
 + 
 +                player.sendMessage(Text.literal("You've used '+ item + "' as a wrong tool: + wrongToolUsedCount + " times.")); 
 +            } 
 +        }); 
 +    } 
 +
 +</code> 
 + 
 +And finally, pass 3 to the custom advancement and create a second one as well. (Dont forget to re-run the gradle task ''**runDatagen**'')
  
-Advancement.Builder.create() +<code java [highlight_lines_extra="12,26"]> 
-        .display(Items.ICE_WAND_1, Text.literal("Used all ice wands"), +AdvancementEntry rootAdvancement = Advancement.Builder.createUntelemetered() 
-                Text.literal("Used all ice wands"), +        .display( 
-                null,+                Items.BELL, 
 +                Text.literal("Wrong Tool Buddy"), 
 +                Text.literal("That's not the right tool"), 
 +                new Identifier("textures/gui/advancements/backgrounds/adventure.png"),
                 AdvancementFrame.TASK,                 AdvancementFrame.TASK,
                 true,                 true,
Line 546: Line 838:
                 false                 false
         )         )
-        .parent(parentAdvancement) +        .criterion("wrong_tool", ExampleMod.WRONG_TOOl.create(new WrongToolCriterion.Conditions(3))) 
-        .criterion("used_all_ice_wands", new WandCategoryUsed.Condition("ice")) +        .build(consumer, "your_mod_id_please_change_me" + "/root");
-        .build(consumer, "your_mod_id_please_change_me" + "/used_all_ice_wands");+
  
-Advancement.Builder.create() +AdvancementEntry second = Advancement.Builder.createUntelemetered().parent(rootAdvancement
-        .display(Items.WATER_WAND_1, Text.literal("Used all water wands"), +        .display( 
-                Text.literal("Used all water wands"), +                Items.QUARTZ, 
-                null,+                Text.literal("You did hear me didn't you?"), 
 +                Text.literal("That's not the right tool"), 
 +                new Identifier("textures/gui/advancements/backgrounds/adventure.png"),
                 AdvancementFrame.TASK,                 AdvancementFrame.TASK,
                 true,                 true,
Line 559: Line 852:
                 false                 false
         )         )
-        .parent(parentAdvancement) +        .criterion("wrong_tool", ExampleMod.WRONG_TOOl.create(new WrongToolCriterion.Conditions(5))) 
-        .criterion("used_all_water_wands", new WandCategoryUsed.Condition("water")) +        .build(consumer, "your_mod_id_please_change_me" + "/root");
-        .build(consumer, "your_mod_id_please_change_me" + "/used_all_water_wands");+
 </code> </code>
  
-Then we'd make sure to register the criterion (logical server side).+If you run the game now (don't forget to re-run the gradle task ''**runDatagen**''), you should see that the advancements are granted when their conditions are met '3' and '5' respectively (actually '4' and '6' because we didn't use '>=').
  
-<code java> +You can also see the conditions section of the ''**root.json**'' file has the variable we wrote ''**amount**'':
-public static WandCategoryUsed WAND_USED = Criteria.register(new WandCategoryUsed()); +
-</code>+
  
-And whenever we detected the player having used all the wands for a particular category, we could trigger the advancement like this: +<code javascript
- +
-<code java+  "criteria": 
-if (playerUsedAllFireWands) +    "wrong_tool": { 
-    WAND_USED.trigger(player, "fire"); // The string here is whatever we initiated the condition with. +      "conditions": 
-+        "amount":
-if (playerUsedAllWaterWands) +      }, 
-    WAND_USED.trigger(player, "water"); // The string here is whatever we initiated the condition with. +      "trigger": "minecraft:your_unique_mod_id_change_me_please/wrong_tool" 
-+    
-if (playerUsedAllIceWands) { +  }, 
-    WAND_USED.trigger(player, "ice"); // The string here is whatever we initiated the condition with.+  // ... (Rest of JSON)
 } }
 </code> </code>
tutorial/datagen_advancements.1696010929.txt.gz · Last modified: 2023/09/29 18:08 by jmanc3