Skip to content

Eventi

Fabric API mette a disposizione un sitema che permette alle mod di reagire ad azioni o accadiementi, definiti come eventi che accadono in gioco.

Gli Eventi sono "agganci" che soddifano usi comuni e/o permettono una compatibilità e performance migliori fra mod diverse che si agganciano alle stesse aree del codice. L'uso degli eventi spesso sostituisce l'uso dei mixin.

Fabric API mette a disposizione eventi per aree importanti del codebase di Minecraft, alle quali molti modder potrebbero essere interessati.

Gli Eventi sono rappresentati da una instance di net.fabricmc.fabric.api.event.Event che contiene e chiama le callbacks. Spesso c'è una singola instance di un evento per una callback, che è conservato in un attributo statico EVENT dell'interfaccia della callback, ma ci sono anche altri casi. Per esempio, ClientTickEvents raggruppa vari eventi di tipi simili insieme.

Callbacks

Le Callbacks sono una parte di codice che viene passata come argomento ad un evento. Quando l'evento viene innescato dal gioco, il pezzo di codice passato viene eseguito.

Interfacce di Callback

Ogni evento ha un'interfaccia di callaback, convenzionalmente chiamata <EventName>Callback. Le Callback sono registrate chiamando il metodo register() in un'instance di un evento, con un'instance dell'interfaccia della callback passata come argomento.

Tutti le interfacce delle callback degli eventi fornite da Fabric API possono essere trovate nel pacchetto net.fabricmc.fabric.api.event.

Ascoltare gli Eventi

Un Semplice Esempio

Questo esempio registra un AttackBlockCallback per danneggiare il giocatore quando colpisce dei blocchi che non droppano un oggetto quando rotti a mani nude.

java
AttackBlockCallback.EVENT.register((player, world, hand, pos, direction) -> {
	BlockState state = world.getBlockState(pos);

	// Manual spectator check is necessary because AttackBlockCallbacks fire before the spectator check
	if (!player.isSpectator() && player.getMainHandStack().isEmpty() && state.isToolRequired()) {
		player.damage(world.getDamageSources().generic(), 1.0F);
	}

	return ActionResult.PASS;
});

Aggiungere Oggetti alle Loot Table esistenti

A volte potresti voler aggiungere oggetti alle loot tables. Ad esempio, aggiungere i tuoi drop ad un blocco o ad un'entità vanilla.

La soluzione più semplice, rimpiazzare il file delle loot table, può rompere altre mod. E se volessero cambiarli anche loro? Daremo un'occhiata a come puoi aggiungere oggetti alle loot tables senza sovrascriverle.

Aggiungeremo delle uova alla loot table del carbone.

Ascoltare il caricamento delle Loot Table

Fabric API ha un evento che viene attivato quando le loot tables sono caricate, LootTableEvents.MODIFY. Puoi registrare una callbak per quello nel tuo mod initializer. Controlliamo anche la loot table corrente sia quella quella del minerale di carbone.

java
// 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(id)) {

Aggiungere Oggetti alla Loot Table

Nelle loot table, gli oggetti sono conservati in loot pool entries, e le voci sono conservate in loot pools. Per aggiungere un oggetto, dovremo aggiungere una pool con una voce oggetto alla loot table.

Possiamo creare una pool con LootPool#builder, e aggiungerla alla loot table.

La nostra pool non ha nemmeno un oggetto, quindi dovremo creare una voce oggetto usando ItemEntry#builder e aggiungerla alla pool.

java
LootTableEvents.MODIFY.register((resourceManager, lootManager, id, tableBuilder, source) -> {
	// 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(id)) {
		// We make the pool and add an item
		LootPool.Builder poolBuilder = LootPool.builder().with(ItemEntry.builder(Items.EGG));
		tableBuilder.pool(poolBuilder);
	}
});

Eventi Custom

Alcune aree del gioco non hanno agganci forniti da Fabric API, quindi dovrai usare un mixin o creare il tuo evento.

Guardermo come creare un evento che viene innescato quando una pecora viene tosata. Il processo per la creazione di un evento è:

  • Creare l'interfaccia di callback per l'evento
  • Innescare l'evento da un mixin
  • Creare un'implementazione di prova

Creare l'interfaccia di Callback per l'Evento

L'interfaccia di callback descrive cosa deve essere implementata dagli event listener, che ascolteranno il tuo evento. L'interfaccia di callback descrive anche come l'evento verrà chiamato dal tuo mixin. È convezione posizionare un oggetto Event come agromento nell'interfaccia di callback, che identificherà un evento effettivo.

Per la nostra implementazione di Event sceglieremo di usare un evento basato sui vettori. Il vettore conterrà tutti gli event listners che stanno ascoltando l'evento.

La nostra implementazione chiamerà gli event listeners i ordine, fino a che uno non ritrona un ActionResult.PASS. Questo vuol dire che un listener può dire "cancella questo", "approva questo" o "non m'interessa, lascialo al prossimo listener".

Usare ActionResult come valore di return, è una maniera convenzionale per fare cooperare gestori di eventi in questa maniera.

Dovrai crare un'intefaccia che ha un'istanza Event e un metodo per l'implementazione della risposta. Un setup semplice per la nostra callback per la tosatura della pecora è:

java
public interface SheepShearCallback {
	Event<SheepShearCallback> EVENT = EventFactory.createArrayBacked(SheepShearCallback.class,
			(listeners) -> (player, sheep) -> {
				for (SheepShearCallback listener : listeners) {
					ActionResult result = listener.interact(player, sheep);

					if (result != ActionResult.PASS) {
						return result;
					}
				}

				return ActionResult.PASS;
			});

	ActionResult interact(PlayerEntity player, SheepEntity sheep);
}

Diamogli un'occhiata più precisa. Quando l'invocatore viene chiamato, iteriamo su tutti i listener:

java
(listeners) -> (player, sheep) -> {
	for (SheepShearCallback listener : listeners) {

Poi chiamiamo il nostro metodo (in questo caso, interact) sul listener per ottenera la sua risposta:

java
ActionResult interact(PlayerEntity player, SheepEntity sheep);

Se il listener dice che dobbiamo cancellare (ActionResult.FAIL), oppure finire completamente (ActionResult.SUCCESS), la callback restituisce il risultato e finische il loop. ActionResult.PASS si sposta sul prossimo listener, e nella maggior parte dei casi dovrebbe risultare un successo se non ci sono altri listener registrati:

java
	if (result != ActionResult.PASS) {
		return result;
	}
}

return ActionResult.PASS;

Possiamo aggiungere commenti Javadoc in cima alle classi di callback per documentare cosa ogni \`ActionResult fa. Nel nostro caso, potrebbe essere:

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.
 */

Innescare l'Evento da un Mixin

Ora abbiamo lo scheltro di base dell'evento, ma nessuno modo di innescarlo. Siccome vogliamo avere l'evento chiamato quando un giocatore prova a tosare una pecora, chiamiamo l'event invoker dentro SheepEntity#interactMob quando sheared() viene chiamato (ovvero quando la pecora può essere tosata, e il giocatore sta tendendo delle cesoie):

java
@Mixin(SheepEntity.class)
public class SheepEntityMixin {
	@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/passive/SheepEntity;sheared(Lnet/minecraft/sound/SoundCategory;)V"), method = "interactMob", cancellable = true)
	private void onShear(final PlayerEntity player, final Hand hand, final CallbackInfoReturnable<ActionResult> info) {
		ActionResult result = SheepShearCallback.EVENT.invoker().interact(player, (SheepEntity) (Object) this);

		if (result == ActionResult.FAIL) {
			info.setReturnValue(result);
		}
	}
}

Creare un Implementazione di Prova

Ora dobbiamo testare il nostro evento. Puoi registrare un listener nel tuo metodo di inizalizzazione (o in un'altra area, se preferisci) e aggiungere la logica custom lì. Qui c'è un esempio che droppa un diamante anziché lana ai piedi della pecora:

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.getWorld(), sheep.getX(), sheep.getY(), sheep.getZ(), stack);
	player.getWorld().spawnEntity(itemEntity);

	return ActionResult.FAIL;
});

Se entri nel gioco e tosi una percora, un diamante dovrebbe essere droppato anziché lana.