🇷🇺 Русский (Russian)
🇷🇺 Русский (Russian)
Внешний вид
🇷🇺 Русский (Russian)
🇷🇺 Русский (Russian)
Внешний вид
This page is written for:
1.21
This page is written for:
1.21
Fabric API предоставляет систему, которая позволяет модам реагировать на действия или явления, так же известные как события, происходящие в игре.
События — это хуки, которые удовлетворяют общим сценариям использования и/или обеспечивают улучшенную совместимость и производительность между модами, которые подключаются к одним и тем же областям кода. Использование событий часто заменяет использование mixins.
API Fabric предоставляет события для важных областей кодовой базы Minecraft, в которых могут быть заинтересованы многие разработчики модов.
События представлены экземплярами net.fabricmc.fabric.api.event.Event
, которые хранят и вызывают обртный вызов (callback). Часто для обратного вызова существует один экземпляр события, который хранится в статическом поле EVENT
интерфейса обратного вызова, но существуют и другие шаблоны. Например, ClientTickEvents
группирует несколько связанных событий вместе.
Обратный вызов — это фрагмент кода, который передается в качестве аргумента событию. Когда игра инициирует событие, переданный фрагмент кода будет выполнен.
Каждое событие имеет соответствующий интерфейс обратного вызова, условно называемый <EventName>Callback
. Обратные вызовы регистрируются путем вызова метода register()
для экземпляра события с экземпляром интерфейса обратного вызова в качестве аргумента.
Все интерфейсы обратного вызова событий, предоставляемые Fabric API, можно найти в пакете net.fabricmc.fabric.api.event
.
В этом примере регистрируется AttackBlockCallback
для нанесения урона игроку при столкновении с блоками, из которых не выпадает предмет при ручной добыче.
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;
});
Иногда вам может понадобиться добавить предметы в таблицы добычи. Например, добавляя свои капли в ванильный блок или сущность.
Самое простое решение — замена файла таблицы добычи — это может сломать другие моды. А что, если они захотят изменить и их? Мы рассмотрим, как можно добавлять предметы в таблицы добычи, не переопределяя таблицу.
Мы добавим особый элемент в таблицу лута угольной руды.
В API Fabric есть событие, которое запускается при загрузке таблиц добычи, LootTableEvents.MODIFY
. Вы можете зарегистрировать обратный вызов для него в инициализаторе мода. Давайте также проверим, что текущая таблица добычи — это таблица добычи угольной руды.
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.
В таблицах лута предметы хранятся в _элементах лут пулов, а элементы хранятся в лут пулах. Чтобы добавить предмет, нам нужно добавить пул с записью предмета в таблицу добычи.
Мы можем создать пул с помощью LootPool#builder
и добавить его в таблицу добычи.
В нашем пуле также нет элементов, поэтому мы создадим запись элемента с помощью ItemEntry#builder
и добавим ее в пул.
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.builder().with(ItemEntry.builder(Items.EGG));
tableBuilder.pool(poolBuilder);
}
});
В некоторых областях игры отсутствуют хуки, предоставляемые API Fabric, поэтому вы можете либо использовать mixin, либо создать собственное событие.
Мы рассмотрим создание события, которое запускается при стрижке овец. Процесс создания события:
Интерфейс обратного вызова описывает, что должно быть реализовано слушателем событий, которые будут прослушивать ваше событие. Интерфейс обратного вызова также описывает, как событие будет вызываться из нашего mixin. Традиционно, объект Event
помещают в качестве поля в интерфейс обратного вызова, который будет идентифицировать наше фактическое событие.
Для нашей реализации Event
мы выберем использование события на основе массива. Массив будет содержать все слушатели событий, которые прослушивают событие.
Наша реализация будет вызывать слушатели событий по порядку до тех пор, пока один из них не вернет ActionResult.PASS
. Это означает, что слушатель может сказать «отменить это», «одобрить это» или «неважно, оставьте это следующему слушателю событий», используя возвращаемое значение.
Использование ActionResult
в качестве возвращаемого значения — это общепринятый способ заставить обработчики событий взаимодействовать таким образом.
Вам необходимо создать интерфейс, имеющий экземпляр «События» и метод для реализации ответа. Базовая настройка обратного вызова для стрижки овец выглядит следующим образом:
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);
}
Давайте рассмотрим это более подробно. При вызове мы перебираем всех слушателей:
(listeners) -> (player, sheep) -> {
for (SheepShearCallback listener : listeners) {
Затем мы вызываем наш метод (в данном случае interact
) для слушателя, чтобы получить его ответ:
ActionResult interact(PlayerEntity player, SheepEntity sheep);
Если слушатель сообщает, что нам необходимо отменить (ActionResult.FAIL
) или завершить (ActionResult.SUCCESS
), обратный вызов возвращает результат и завершает цикл. ActionResult.PASS
переходит к следующему слушателю и в большинстве случаев должен завершиться успешно, если больше нет зарегистрированных слушателей:
if (result != ActionResult.PASS) {
return result;
}
}
return ActionResult.PASS;
Мы можем добавить комментарии Javadoc в начало классов обратного вызова, чтобы документировать, что делает каждый ActionResult
. В нашем случае это может быть:
/**
* 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.
*/
Теперь у нас есть базовый скелет события, но нам нужно его запустить. Поскольку мы хотим, чтобы событие вызывалось, когда игрок пытается постричь овцу, мы вызываем событие invoker
в SheepEntity#interactMob
, когда вызывается sheared()
(т. е. овцу можно стричь, а игрок держит ножницы):
@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);
}
}
}
Теперь нам нужно протестировать наше событие. Вы можете зарегистрировать слушатель в своем методе инициализации (или в другой области, если вам так удобнее) и добавить туда пользовательскую логику. Вот пример, в котором вместо шерсти к ногам овцы падает алмаз:
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;
});
Если вы зайдете в игру и пострижете овцу, вместо шерсти должен выпасть алмаз.