VORAUSSETZUNGEN
Stelle sicher, dass du den Prozess der Einrichtung der Datengenerierung zuerst abgeschlossen hast.
Einrichten
Zuerst müssen wir unseren Provider erstellen. Erstelle eine Klasse, die extends FabricAdvancementProvider beinhaltet und fülle die Basismethoden aus:
java
public class FabricDocsReferenceAdvancementProvider extends FabricAdvancementProvider {
protected FabricDocsReferenceAdvancementProvider(FabricDataOutput output, CompletableFuture<RegistryWrapper.WrapperLookup> registryLookup) {
super(output, registryLookup);
}
@Override
public void generateAdvancement(RegistryWrapper.WrapperLookup wrapperLookup, Consumer<AdvancementEntry> consumer) {
}
}1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Um die Einrichtung abzuschließen, füge den Provider zu deinem DataGeneratorEntrypoint in der onInitializeDataGenerator Methode hinzu.
java
pack.addProvider(FabricDocsReferenceAdvancementProvider::new);1
Struktur eines Fortschritts
Ein Fortschritt setzt sich aus mehreren Komponenten zusammen. Neben den Voraussetzungen, auch als "Kriterien" bezeichnet, kann er auch folgendes haben:
- Ein
AdvancementDisplay, das dem Spiel mitteilt, wie der Fortschritt den Spielern angezeigt werden soll, AdvancementRequirements, bei denen es sich um Listen von Kriterien handelt, von denen mindestens ein Kriterium aus jeder Teilliste erfüllt sein muss,AdvancementRewards, die der Spieler für den Abschluss des Fortschritts erhält.- Ein
CriterionMerger, der dem Fortschritt mitteilt wie er mehrere Kriterien verarbeiten soll, und - Ein übergeordnetes
Advancement, das die Hierachie organisiert, welche du in dem "Fortschritt" Fenster sehen kannst.
Einfacher Fortschritt
Hier ist ein einfacher Fortschritt, um einen Erdblock zu erhalten:
java
AdvancementEntry getDirt = Advancement.Builder.create()
.display(
Items.DIRT, // The display icon
Text.literal("Your First Dirt Block"), // The title
Text.literal("Now make a house from it"), // The description
Identifier.ofVanilla("textures/gui/advancements/backgrounds/adventure.png"), // Background image for the tab in the advancements page, if this is a root advancement (has no parent)
AdvancementFrame.TASK, // TASK, CHALLENGE, or GOAL
true, // Show the toast when completing it
true, // Announce it to chat
false // Hide it in the advancement tab until it's achieved
)
// "got_dirt" is the name referenced by other advancements when they want to have "requirements."
.criterion("got_dirt", InventoryChangedCriterion.Conditions.items(Items.DIRT))
// Give the advancement an id
.build(consumer, FabricDocsReference.MOD_ID + ":get_dirt");1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
WARNING
Denke bei der Erstellung deiner Fortschrittseinträge daran, dass die Funktion den Identifier des Fortschritts im Format String annimmt!
JSON Ausgabe
json
{
"criteria": {
"got_dirt": {
"conditions": {
"items": [
{
"items": "minecraft:dirt"
}
]
},
"trigger": "minecraft:inventory_changed"
}
},
"display": {
"background": "minecraft:textures/gui/advancements/backgrounds/adventure.png",
"description": "Now make a house from it",
"icon": {
"count": 1,
"id": "minecraft:dirt"
},
"title": "Your First Dirt Block"
},
"requirements": [
[
"got_dirt"
]
],
"sends_telemetry_event": true
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Ein weiteres Beispiel
Um den Dreh raus zu bekommen, fügen wir noch einen weiteren Fortschritt hinzu. Wir üben das Hinzufügen von Belohnungen, die Verwendung mehrerer Kriterien und die Zuweisung von Eltern:
java
final RegistryWrapper.Impl<Item> itemLookup = wrapperLookup.getOrThrow(RegistryKeys.ITEM);
AdvancementEntry appleAndBeef = Advancement.Builder.create()
.parent(getDirt)
.display(
Items.APPLE,
Text.literal("Apple and Beef"),
Text.literal("Ate an apple and beef"),
null, // Children don't need a background, the root advancement takes care of that
AdvancementFrame.CHALLENGE,
true,
true,
false
)
.criterion("ate_apple", ConsumeItemCriterion.Conditions.item(itemLookup, Items.APPLE))
.criterion("ate_cooked_beef", ConsumeItemCriterion.Conditions.item(itemLookup, Items.COOKED_BEEF))
.build(consumer, FabricDocsReference.MOD_ID + ":apple_and_beef");1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
Benutzdefinierte Kriterien
WARNING
Während der Datengenerator auf der Client-Seite liegen kann, befinden sich Criterions und Predicates im Hauptquellenverzeichnis (beide Seiten), da der Server sie auslösen und auswerten muss.
Definition
Ein Kriterium (Plural: Kriterien) ist etwas, was Spieler machen können (oder was einem Spieler passieren kann) was möglicherweise einem Fortschritt angerechnet wird. Das Spiel kommt mit vielen Kriterien, welche in dem net.minecraft.advancement.criterion Packet gefunden werden können. Generell musst du nur ein neues Kriterium hinzufügen, wenn du eine benutzdefinierte Mechanik zum Spiel hinzufügst.
Bedingungen werden von Kriterien ausgewertet. Ein Kriterium wird nur gezählt, wenn alle relevanten Bedingungen zutreffen. Bedingungen werden in der Regel durch ein Prädikat ausgedrückt.
Ein Prädikat ist etwas, das einen Wert entgegennimmt und einen boolean zurück gibt. Zum Beispiel, ein Predicate<Item> gibt möglicherweise true zurück, wenn das Item ein Diamant ist, während ein Predicate<LivingEntity> möglicherweise true zurückgibt, wenn die Entität nicht gegenüber einem Dorfbewohner feindlich gesinnt ist.
Erstellen von benutzdefinierten Kriterien
Zuerst müssen wir eine neue Mechanik implementieren. Wir können dem Spieler jedes Mal, wenn er einen Block abbaut, mitteilen, welches Werkzeug er benutzt hat.
java
public class FabricDocsReferenceDatagenAdvancement implements ModInitializer {
@Override
public void onInitialize() {
HashMap<Item, Integer> tools = new HashMap<>();
PlayerBlockBreakEvents.AFTER.register(((world, player, blockPos, blockState, blockEntity) -> {
if (player instanceof ServerPlayerEntity serverPlayer) { // Only triggers on the server side
Item item = player.getMainHandStack().getItem();
Integer usedCount = tools.getOrDefault(item, 0);
usedCount++;
tools.put(item, usedCount);
serverPlayer.sendMessage(Text.of("You've used \"" + item + "\" as a tool " + usedCount + " times!"));
}
}));
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Beachte, dass dieser Code wirklich schlecht ist. Die HashMap wird nirgendwo dauerhaft gespeichert, daher wird sie bei jedem Neustart des Spiels zurückgesetzt. Es geht nur darum, Criterions aufzuzeigen. Starte das Spiel und teste es!
Als Nächstes erstellen wir unser benutzerdefiniertes Kriterium UseToolCriterion. Es wird seine eigene Klasse Conditions benötigen, also werden wir beide auf einmal erstellen:
java
public class UseToolCriterion extends AbstractCriterion<UseToolCriterion.Conditions> {
@Override
public Codec<Conditions> getConditionsCodec() {
return Conditions.CODEC;
}
public record Conditions(Optional<LootContextPredicate> playerPredicate) implements AbstractCriterion.Conditions {
public static Codec<UseToolCriterion.Conditions> CODEC = LootContextPredicate.CODEC.optionalFieldOf("player")
.xmap(Conditions::new, Conditions::player).codec();
@Override
public Optional<LootContextPredicate> player() {
return playerPredicate;
}
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
Puh, das ist eine Menge! Schauen wir uns das mal genauer an.
UseToolCriterionist einAbstractCriterion, auf dasConditionsangewendet werden können.Conditionshat einplayerPredicateFeld. AlleConditionssollten ein Spielerprädikat haben (technisch gesehen einLootContextPredicate).Conditionshaben auch einenCODEC. DieserCodecist einfach der Codec für sein einziges Feld,playerPredicate, mit zusätzlichen Anweisungen zur Konvertierung zwischen ihnen (xmap).
INFO
Um mehr über Codecs zu erfahren, sieh dir die Codecs Seite an.
Wir brauchen einen Weg, um zu überprüfen, ob Bedingungen erfüllt sind. Lasst uns eine Hilfsmethode zu Conditions hinzufügen:
java
public boolean requirementsMet() {
return true; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}1
2
3
4
2
3
4
Da wir nun ein Kriterium und seine Bedingungen haben, brauchen wir eine Möglichkeit, es auszulösen. Füge eine Auslösungsmethode zu UseToolCriterion hinzu:
java
public void trigger(ServerPlayerEntity player) {
trigger(player, Conditions::requirementsMet);
}1
2
3
4
2
3
4
Fast geschafft! Als nächstes benötigen wir eine Instanz unseres Kriteriums, mit der wir arbeiten können. Fügen wir sie in eine neue Klasse mit dem Namen ModCriteria ein.
java
public class ModCriteria {
public static final UseToolCriterion USE_TOOL = Criteria.register(FabricDocsReference.MOD_ID + ":use_tool", new UseToolCriterion());
}1
2
3
4
2
3
4
Um sicherzustellen, dass unsere Kriterien zum richtigen Zeitpunkt initialisiert werden, füge eine leere init-Methode hinzu:
java
// :::datagen-advancements:mod-criteria
public static final UseToolCriterion USE_TOOL = Criteria.register(FabricDocsReference.MOD_ID + ":use_tool", new UseToolCriterion());
// :::datagen-advancements:mod-criteria
// :::datagen-advancements:new-mod-criteria
public static final ParameterizedUseToolCriterion PARAMETERIZED_USE_TOOL = Criteria.register(FabricDocsReference.MOD_ID + ":parameterized_use_tool", new ParameterizedUseToolCriterion());
// :::datagen-advancements:mod-criteria1
2
3
4
5
6
7
2
3
4
5
6
7
Und rufe es in deinem Mod-Initialisierer auf:
java
ModCriteria.init();1
Schließlich müssen wir unsere Kriterien auslösen. Füge dies zu der Stelle hinzu, an der wir in der Hauptmodklasse eine Nachricht an den Spieler geschickt haben.
java
ModCriteria.USE_TOOL.trigger(serverPlayer);1
Dein neues Kriterium ist jetzt einsatzbereit! Lasst es uns zu unserem provider hinzufügen:
java
AdvancementEntry breakBlockWithTool = Advancement.Builder.create()
.parent(getDirt)
.display(
Items.DIAMOND_SHOVEL,
Text.literal("Not a Shovel"),
Text.literal("That's not a shovel (probably)"),
null,
AdvancementFrame.GOAL,
true,
true,
false
)
.criterion("break_block_with_tool", ModCriteria.USE_TOOL.create(new UseToolCriterion.Conditions(Optional.empty())))
.build(consumer, FabricDocsReference.MOD_ID + ":break_block_with_tool");1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Führe den Datengenerator Task erneut aus und du hast einen neuen Fortschritt bekommen, mit dem du spielen kannst!
Bedingungen mit Parametern
Das ist alles schön und gut, aber was ist, wenn wir einen Fortschritt nur dann gewähren wollen, wenn wir etwas fünfmal getan haben? Und warum nicht noch einen bei zehn Mal? Hierfür müssen wir unserer Bedingung einen Parameter geben. Du kannst bei UseToolCriterion bleiben, oder du kannst mit einem neuen ParameterizedUseToolCriterion nachziehen. In der Praxis solltest du nur die parametrisierte Variante haben, aber für dieses Tutorial werden wir beide behalten.
Lass uns von unten nach oben arbeiten. Wir müssen prüfen, ob die Anforderungen erfüllt sind, also bearbeiten wir unsere Methode Conditions#requirementsMet:
java
public boolean requirementsMet(int totalTimes) {
return totalTimes > requiredTimes; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}1
2
3
4
2
3
4
requiredTimes existiert nicht, also mache es zu einem Parameter von Conditions:
java
public record Conditions(Optional<LootContextPredicate> playerPredicate, int requiredTimes) implements AbstractCriterion.Conditions {
@Override
public Optional<LootContextPredicate> player() {
return playerPredicate;
}
// :::datagen-advancements:new-requirements-met
public boolean requirementsMet(int totalTimes) {
return totalTimes > requiredTimes; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}
// :::datagen-advancements:new-requirements-met
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Jetzt ist unser Codec fehlerhaft. Lass uns einen neuen Codec für die neuen Änderungen schreiben:
java
public static Codec<ParameterizedUseToolCriterion.Conditions> CODEC = RecordCodecBuilder.create(instance -> instance.group(
LootContextPredicate.CODEC.optionalFieldOf("player").forGetter(Conditions::player),
Codec.INT.fieldOf("requiredTimes").forGetter(Conditions::requiredTimes)
).apply(instance, Conditions::new));
// :::datagen-advancements:new-parameter
@Override
public Optional<LootContextPredicate> player() {
return playerPredicate;
}
// :::datagen-advancements:new-requirements-met
public boolean requirementsMet(int totalTimes) {
return totalTimes > requiredTimes; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}
// :::datagen-advancements:new-requirements-met
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Nun müssen wir unsere trigger-Methode korrigieren:
java
public void trigger(ServerPlayerEntity player, int totalTimes) {
trigger(player, conditions -> conditions.requirementsMet(totalTimes));
}1
2
3
4
2
3
4
Wenn du ein neues Kriterium erstellt hast, müssen wir es zu ModCriteria hinzufügen
java
public static final ParameterizedUseToolCriterion PARAMETERIZED_USE_TOOL = Criteria.register(FabricDocsReference.MOD_ID + ":parameterized_use_tool", new ParameterizedUseToolCriterion());
// :::datagen-advancements:mod-criteria
// :::datagen-advancements:mod-criteria-init
public static void init() {
}1
2
3
4
5
6
7
2
3
4
5
6
7
Und rufe sie in unserer Hauptklasse auf, genau dort, wo die alte Klasse ist:
java
ModCriteria.PARAMETERIZED_USE_TOOL.trigger(serverPlayer, usedCount);1
Füge den Fortschritt zu deinem Provider hinzu:
java
AdvancementEntry breakBlockWithToolFiveTimes = Advancement.Builder.create()
.parent(breakBlockWithTool)
.display(
Items.GOLDEN_SHOVEL,
Text.literal("Not a Shovel Still"),
Text.literal("That's still not a shovel (probably)"),
null,
AdvancementFrame.GOAL,
true,
true,
false
)
.criterion("break_block_with_tool_five_times", ModCriteria.PARAMETERIZED_USE_TOOL.create(new ParameterizedUseToolCriterion.Conditions(Optional.empty(), 5)))
.build(consumer, FabricDocsReference.MOD_ID + ":break_block_with_tool_five_times");1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
Führe den Datengenerator erneut aus, und du bist endlich fertig!




