Events 26.1.2
Ein Leitfaden für die Nutzung von Events, welche von der Fabric API bereitgestellt werden.
Die Fabric API bietet ein System, das es Mods erlaubt, auf Aktionen oder Ereignisse zu reagieren, die auch als Events im Spiel definiert sind.
Events sind Hooks, die gemeinsame Anwendungsfälle erfüllen und/oder die Kompatibilität und Leistung zwischen Mods verbessern, die sich in dieselben Bereiche des Codes einklinken. Die Verwendung von Ereignissen ersetzt oft die Verwendung von Mixins.
Die Fabric API stellt Ereignisse für wichtige Bereiche der Minecraft-Codebasis bereit, an denen mehrere Modder interessiert sein könnten.
Ereignisse werden durch Instanzen von net.fabricmc.fabric.api.event.Event dargestellt, die Callbacks speichern und aufrufen. Oft gibt es eine einzige Event-Instanz für einen Callback, die in einem statischen Attribut EVENT des Callback-Interfaces gespeichert wird, aber es gibt auch andere Muster. Zum Beispiel fasst ClientTickEvents mehrere zusammenhängende Ereignisse zusammen.
Callbacks
Callbacks sind ein Teil des Codes, der als Argument an ein Event übergeben wird. Wenn das Event vom Spiel ausgelöst wird, wird der übergebene Teil des Codes ausgeführt.
Callback Interfaces
Jedes Event hat ein dazugehöriges Callback Interface. Callbacks werden durch den Aufruf der Methode register() für eine Event-Instanz registriert, wobei eine Instanz des Callbacks als Argument angegeben wird.
Auf Events hören
Dieses Beispiel registriert einen AttackBlockCallback, um dem Spieler Schaden zuzufügen, wenn er Blöcke trifft, die keinen Gegenstand fallen lassen, wenn sie von Hand abgebaut werden.
java
AttackBlockCallback.EVENT.register((player, level, hand, pos, direction) -> {
BlockState state = level.getBlockState(pos);
// Manual spectator check is necessary because AttackBlockCallbacks fire before the spectator check
if (!player.isSpectator() && player.getMainHandItem().isEmpty() && state.requiresCorrectToolForDrops() && level instanceof ServerLevel serverLevel) {
player.hurtServer(serverLevel, level.damageSources().generic(), 1.0F);
}
return InteractionResult.PASS;
});1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Items zu existierenden Beutetabellen hinzufügen
Manchmal willst du vielleicht Gegenstände zu Beutetabellen hinzufügen. Zum Beispiel, indem du deine Drops zu einem Vanille-Block oder einer Entität hinzufügst.
Die einfachste Lösung, das Ersetzen der Beutetabellen-Datei, kann andere Mods zerstören. Was ist, wenn sie diese auch ändern wollen? Wir sehen uns an, wie du Gegenstände zu Beutetabellen hinzufügen kannst, ohne die Tabelle zu überschreiben.
Wir werden die Beutetabelle für Kohleerz um Eier erweitern.
Auf das Laden der Beutetabelle hören
Die Fabric API hat ein Event, das ausgelöst wird, wenn Beutetabellen geladen werden, LootTableEvents.MODIFY. Du kannst hierfür einen Callback in deinem Mod Initialisierer registrieren. Überprüfen wir auch, ob die aktuelle Beutetabelle die Beutetabelle für Kohleerz ist:
java
LootTableEvents.MODIFY.register((key, tableBuilder, source, registries) -> {
// Let's only modify built-in loot tables and leave data pack loot tables untouched by checking the source.
// We also check that the loot table ID is equal to the ID we want.
if (source.isBuiltin() && COAL_ORE_LOOT_TABLE_ID.equals(key)) {
// ...
}
});1
2
3
4
5
6
7
2
3
4
5
6
7
Hinzufügen von Items zur Beutetabelle
Um einen Gegenstand hinzuzufügen, müssen wir der Beutetabelle einen Pool mit einem Eintrag für ein Item hinzufügen.
Wir können einen Pool mit LootPool#lootPool erstellen, und ihn zur Beutetabelle hinzufügen.
Unser Pool hat auch noch keine Items, also erstellen wir einen Item-Eintrag mit LootItem#lootTableItem und fügen ihn dem Pool hinzu.
java
LootTableEvents.MODIFY.register((key, tableBuilder, source, registries) -> {
// Let's only modify built-in loot tables and leave data pack loot tables untouched by checking the source.
// We also check that the loot table ID is equal to the ID we want.
if (source.isBuiltin() && COAL_ORE_LOOT_TABLE_ID.equals(key)) {
// We make the pool and add an item
LootPool.Builder poolBuilder = LootPool.lootPool().add(LootItem.lootTableItem(Items.EGG));
tableBuilder.withPool(poolBuilder);
}
});1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
Benutzerdefinierte Events
In einigen Bereichen des Spiels gibt es keine von der Fabric API bereitgestellten Hooks, so dass du entweder ein Mixin verwenden oder dein eigenes Event erstellen kannst.
Wir werden uns ein Event ansehen, das ausgelöst wird, wenn Schafe geschoren werden. Der Prozess der Erstellung eines Events ist wie folgt:
- Erstellen des Interface für einen Event Callback
- Auslösen des Events von einem Mixin
- Erstellen einer Testimplementierung
Erstellen des Interface für einen Event Callback
Das Callback-Interface beschreibt, was von den Event-Listenern implementiert werden muss, die auf dein Event hören werden. Das Callback-Interface beschreibt auch, wie das Event von unserem Mixin ausgelöst werden soll. Es ist üblich, ein Event-Objekt als Attribut in dem Callback-Interface zu platzieren, das unser tatsächliches Event identifiziert.
Für unsere Event-Implementierung werden wir uns für ein Array-gestütztes Event entscheiden. Das Array enthält alle Event-Listener, die auf das Event hören.
Unsere Implementierung ruft die Event-Listener der Reihe nach auf, bis einer von ihnen kein InteractionResult.PASS zurückgibt. Das bedeutet, dass ein Listener mit Hilfe seines Rückgabewerts "dies abbrechen", "dies genehmigen" oder "mir egal, an den nächsten Event-Listener weiterleiten" sagen kann.
Die Verwendung von InteractionResult als Rückgabewert ist eine gängige Methode, um Event-Handler auf diese Weise miteinander zu koordinieren.
Du musst ein Interface erstellen, das eine Event-Instanz und eine Methode zur Implementierung der Antwort hat. Ein Grundaufbau für unseren Schafschur-Callback ist:
java
public interface SheepShearCallback {
Event<SheepShearCallback> EVENT = EventFactory.createArrayBacked(SheepShearCallback.class,
(listeners) -> (player, sheep) -> {
for (SheepShearCallback listener : listeners) {
InteractionResult result = listener.interact(player, sheep);
if (result != InteractionResult.PASS) {
return result;
}
}
return InteractionResult.PASS;
}
);
InteractionResult interact(Player player, Sheep sheep);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Schauen wir uns das genauer an. Wenn der Invoker aufgerufen wird, wird über alle Listener iteriert:
java
(listeners) -> (player, sheep) -> {
for (SheepShearCallback listener : listeners) {
// ...
}
}1
2
3
4
5
2
3
4
5
Bei jedem Listener rufen wir dann interact auf, um die Antwort des Listeners abzurufen. Hier ist die Signatur von interact, die wir in diesem Interface deklariert haben:
java
InteractionResult interact(Player player, Sheep sheep);1
Wenn der Listener sagt, dass wir abbrechen (durch die Rückgabe von FAIL) oder vollständig beenden (SUCCESS) müssen, gibt der Callback das Ergebnis zurück und beendet die Schleife.
InteractionResult.PASS wird an den nächsten Listener weitergeleitet, bis alle Listener aufgerufen wurden und schließlich PASS zurückgegeben wird:
java
if (result != InteractionResult.PASS) {
return result;
}
return InteractionResult.PASS;1
2
3
4
2
3
4
Wir können Javadoc-Kommentare an der obersten Stelle der Callback-Klassen hinzufügen, um zu dokumentieren, was jedes InteractionResult macht. In unserem Fall könnte das wie folgt sein:
java
/**
* Callback for shearing a sheep.
* Called before the sheep is sheared, items are dropped, and items are damaged.
* Upon return:
* - SUCCESS cancels further processing and continues with normal shearing behavior.
* - PASS falls back to further processing and defaults to SUCCESS if no other listeners are available
* - FAIL cancels further processing and does not shear the sheep.
*/1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
Auslösen des Events von einem Mixin
Jetzt haben wir das Grundgerüst für ein Event, aber wir müssen es auslösen. Da wir das Event auslösen wollen, wenn ein Spieler versucht, ein Schaf zu scheren, rufen wir den invoker des Event in Sheep#mobInteract auf, wenn shear() aufgerufen wird (d. h. Schafe können geschoren werden, und der Spieler hält eine Schere):
java
@Mixin(Sheep.class)
public class SheepMixin {
@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/animal/sheep/Sheep;shear(Lnet/minecraft/server/level/ServerLevel;Lnet/minecraft/sounds/SoundSource;Lnet/minecraft/world/item/ItemStack;)V"), method = "mobInteract", cancellable = true)
private void onShear(final Player player, final InteractionHand hand, final CallbackInfoReturnable<InteractionResult> info) {
InteractionResult result = SheepShearCallback.EVENT.invoker().interact(player, (Sheep) (Object) this);
if (result == InteractionResult.FAIL) {
info.setReturnValue(result);
}
}
}1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
Erstellen einer Testimplementierung
Jetzt müssen wir unser Event testen. Du kannst einen Listener in deiner Initialisierungsmethode (oder in einem anderen Bereich, wenn du das bevorzugst) registrieren und dort benutzerdefinierte Logik hinzufügen.
Hier ist ein Beispiel, bei dem dem ein Diamant anstelle von Wolle auf die Füße des Schafs fällt:
java
SheepShearCallback.EVENT.register((player, sheep) -> {
sheep.setSheared(true);
// Create diamond item entity at sheep's position.
ItemStack stack = new ItemStack(Items.DIAMOND);
ItemEntity itemEntity = new ItemEntity(player.level(), sheep.getX(), sheep.getY(), sheep.getZ(), stack);
player.level().addFreshEntity(itemEntity);
return InteractionResult.FAIL;
});1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
Wenn du im Spiel ein Schaf scherst, sollte anstelle von Wolle ein Diamant fallen.












