🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
🇩🇪 Deutsch (German)
🇩🇪 Deutsch (German)
Erscheinungsbild
Diese Seite ist für folgende Version geschrieben:
1.21.4
Diese Seite ist für folgende Version geschrieben:
1.21.4
VORAUSSETZUNGEN
Stelle sicher, dass du den Prozess der Einrichtung der Datengenerierung zuerst abgeschlossen hast.
Zuerst müssen wir unseren Provider erstellen. Erstelle eine Klasse, die extends FabricAdvancementProvider
beinhaltet und fülle die Basismethoden aus:
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) {
}
}
Um die Einrichtung abzuschließen, füge den Provider zu deinem DataGeneratorEntrypoint
in der onInitializeDataGenerator
Methode hinzu.
pack.addProvider(FabricDocsReferenceAdvancementProvider::new);
Ein Fortschritt setzt sich aus mehreren Komponenten zusammen. Neben den Voraussetzungen, auch als "Kriterien" bezeichnet, kann er auch folgendes haben:
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.CriterionMerger
, der dem Fortschritt mitteilt wie er mehrere Kriterien verarbeiten soll, undAdvancement
, das die Hierachie organisiert, welche du in dem "Fortschritt" Fenster sehen kannst.Hier ist ein einfacher Fortschritt, um einen Erdblock zu erhalten:
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");
WARNING
Denke bei der Erstellung deiner Fortschrittseinträge daran, dass die Funktion den Identifier
des Fortschritts im Format String
annimmt!
{
"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
}
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:
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(wrapperLookup.getOrThrow(RegistryKeys.ITEM), Items.APPLE))
.criterion("ate_cooked_beef", ConsumeItemCriterion.Conditions.item(itemLookup, Items.COOKED_BEEF))
.build(consumer, FabricDocsReference.MOD_ID + ":apple_and_beef");
WARNING
Während der Datengenerator auf der Client-Seite liegen kann, befinden sich Criterion
s und Predicate
s im Hauptquellenverzeichnis (beide Seiten), da der Server sie auslösen und auswerten muss.
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.
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.
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!"));
}
}));
}
}
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, Criterion
s 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:
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;
}
}
}
Puh, das ist eine Menge! Schauen wir uns das mal genauer an.
UseToolCriterion
ist ein AbstractCriterion
, auf das Conditions
angewendet werden können.Conditions
hat ein playerPredicate
Feld. Alle Conditions
sollten ein Spielerprädikat haben (technisch gesehen ein LootContextPredicate
).Conditions
haben auch einen CODEC
. Dieser Codec
ist 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:
public boolean requirementsMet() {
return true; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}
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:
public void trigger(ServerPlayerEntity player) {
trigger(player, Conditions::requirementsMet);
}
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.
public class ModCriteria {
public static final UseToolCriterion USE_TOOL = Criteria.register(FabricDocsReference.MOD_ID + "/use_tool", new UseToolCriterion());
}
Um sicherzustellen, dass unsere Kriterien zum richtigen Zeitpunkt initialisiert werden, füge eine leere init
-Methode hinzu:
// :::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-criteria
Und rufe es in deinem Mod-Initialisierer auf:
ModCriteria.init();
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.
ModCriteria.USE_TOOL.trigger(serverPlayer);
Dein neues Kriterium ist jetzt einsatzbereit! Lasst es uns zu unserem provider hinzufügen:
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");
Führe den Datengenerator Task erneut aus und du hast einen neuen Fortschritt bekommen, mit dem du spielen kannst!
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:
public boolean requirementsMet(int totalTimes) {
return totalTimes > requiredTimes; // AbstractCriterion#trigger helpfully checks the playerPredicate for us.
}
requiredTimes
existiert nicht, also mache es zu einem Parameter von Conditions
:
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
}
}
Jetzt ist unser Codec fehlerhaft. Lass uns einen neuen Codec für die neuen Änderungen schreiben:
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
}
}
Nun müssen wir unsere trigger
-Methode korrigieren:
public void trigger(ServerPlayerEntity player, int totalTimes) {
trigger(player, conditions -> conditions.requirementsMet(totalTimes));
}
Wenn du ein neues Kriterium erstellt hast, müssen wir es zu ModCriteria
hinzufügen
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() {
}
Und rufe sie in unserer Hauptklasse auf, genau dort, wo die alte Klasse ist:
ModCriteria.PARAMETERIZED_USE_TOOL.trigger(serverPlayer, usedCount);
Füge den Fortschritt zu deinem Provider hinzu:
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");
Führe den Datengenerator erneut aus, und du bist endlich fertig!