Skip to content

Eventos

El Fabric API provee un sistema que le permite a los mods reaccionar a ciertas acciones u ocurrencias, definidas como eventos que ocurren en el juego.

Los eventos son enganches que satisfacen usos comunes y/o proveen mejor compatibilidad y rendimiento entre diferentes mods que se enganchan a las mismas áreas del código. El uso de eventos substituye el uso de mixins.

El Fabric API provee eventos para áreas importantes en el código fuente de Minecraft, las cuales son de interés a desarrolladores de mods para engancharse en.

Los eventos son representados por instancias de net.fabricmc.fabric.api.event.Event, el cual guarda y también llama callbacks. Usualmente solo hay una instancia del evento para un callback, el cual es almacenado en un miembro estático EVENT en la interfaz del callback, pero también hay otros patrones. Por ejemplo, ClientTickEvents agrupa varios eventos relacionados juntos.

Callbacks

Un callback es una porción de código pasada como un argumento a un evento. Cuando el evento es activado por el juego, el código pasado será ejecutado.

Interfaces de Callbacks

Cada evento tiene su propia interfaz de callback, convencionalmente llamada <EventName>Callback. Los callbacks son registrados llamando el método register() en una instancia del evento, con una instancia de la interfaz del callback como el argumento.

Todas las interfaces de callbacks para eventos proveídas por el Fabric API pueden ser encontradas en el paquete net.fabricmc.fabric.api.event.

Detectando Eventos

Un Ejemplo Sencillo

Este ejemplo registra un AttackBlockCallback para atacar el jugador cuando este golpea bloques que no sueltan un item cuando son minados con la mano.

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;
});

Añadiendo Items a Loot Tables Existentes

A veces, uno desea añadir items a loot tables (o tablas que determinan los items soltados por un objeto, como bloque o entidad). Por ejemplo, añadir tus drops a un bloque o entidad vanilla.

La solución más simple, la cual es reemplazar el archivo del loot table, puede romper otros mods. ¿Qué tal si otros mods también quierer cambiar el loot table? Echaremos un vistazo a como puedes agregar items a los loot tables sin tener que anularla.

Agregaremos huevos al loot table del bloque de mena de hierro.

Detectando el Cargado de Loot Tables

El Fabric API tiene un evento que es llamado cuando los loot tables son cargados, llamado LootTableEvents.MODIFY. Puedes registrar un callback para el evento en tu inicializador de mod. Verifiquemos también que el loot table actual sea el del bloque de mena de hierro.

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)) {

Agregando Items al Loot Table

En los loot tableas, los items son almacenados en loot pool entries (o entradas en el grupo de loots), y las entradas son guardadas en loot pools (o grupos de loot). Para agregar un item, necesitaremos agregar un grupo con una entrada de item al loot table.

Podemos crear un grupo con LootPool#builder, y agregarlo al loot table.

Nuestro grupo no tiene ningún item, asique haremos una entrada de item usando ItemEntry#builder, y la agregaremos al grupo.

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);
	}
});

Eventos Personalizados

Algunas áreas del juego no tienen enganches proveídos por el Fabric API, así que puedes usar un mixin o puedes crear tu propio evento.

Veremos como crear un evento que es llamado cuando una oveja es esquilada. El proceso de creación de un evento es:

  • Crear la interfaz de callback del evento
  • Activando el evento desde un mixin
  • Crear una implementación de prueba

Crear la interfaz de callback del evento

La interfaz de callback describe lo que debe ser implementado por los usuarios del evento que detectarán tu evento. La interfaz del callback también describe como el evento será llamado desde nuestro mixin. Es convencional poner un objecto Event como un miembro en la interfaz de callback, el cual identificará nuestro evento.

Para nuestra implementación de Event, escogeremos usar un evento respaldado por un array (array-backed event). El array contendrá todos los escuchadores de evento que están detectando este evento.

Nuestra implementación llamará los escuchadores de evento en orden hasta que uno de ellos no retorne ActionResult.PASS. Esto signfica que un usuario puede decir "cancela esto", "aprueba esto" o "no me importa, déjaselo al siguiente escuchador del evento" usando el valor retornado.

Usar ActionResult como valor de retorno es una manera convencional para hacer que los diferentes usuarios del evento cooperen de esta manera.

Necesitarás crear una interfaz que tiene una instancia de Event y un método para la implementación de la respuesta. Una configuración básica para nuestro callback de esquilado de oveja es:

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);
}

Veamos este códigdo con más detalle. Cuando el invocador es llamado, iteramos sobre todos los escuchadores:

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

Entonces llamamos nuestro método (en este caso, interact), en el escuchador para obtener su respuesta:

java
ActionResult interact(PlayerEntity player, SheepEntity sheep);

Si el escuchador dice que tenemos que cancelar (ActionResult.FAIL) o terminar completamente (ActionResult.SUCCESS), el callback retorna el resultado y termina el loop. ActionResult.PASS prosigue al siguiente escuchador, y en la mayoría de los casos debería resultar en éxito si no hay más escuchadores registrados:

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

return ActionResult.PASS;

Podemos agregar Javadocs por encima de las clases de callback para documentar que es lo que hace cada ActionResult. En nuestro caso, puede ser:

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

Activando el Evento Desde un Mixin

Ya tenemos el esqueleto básico de nuestro evento, pero necesitamos llamarlo o activarlo. Ya que queremos que nuestro evento sea llamado cuando un jugador trata de esquilar una oveja, podemos llamado el invocador invoker del evento en SheepEntity#interactMob, cuando el método sheared() es llamado (osea que la oveja puede ser esquilada, y el jugador está sosteniendo tijeras):

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);
		}
	}
}

Creando una Implementación de Prueba

Ahora necesitamos probar nuestro evento. Puedes registrar un escuchador en tu método de inicialización (o en otro lugar, si lo prefieres) y poner tu propio código ahí. En este ejemplo, cuando la oveja es esquilada, suelta un diamante en vez de lana:

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;
});

Si entras al juego y esquilas una oveja, un diamante debería ser soltado en vez de lana.