From 125494399db067f9ee9dbb319ab078e4903ab3ce Mon Sep 17 00:00:00 2001 From: lazynessmind <52864251+lazynessmind@users.noreply.github.com> Date: Wed, 23 Jun 2021 17:46:14 +0100 Subject: [PATCH] Entity Event: Animal Tame (#109) --- .../event/events/common/EntityEvent.java | 20 ++++++++ .../mixin/fabric/HorseTameInvoker.java | 50 +++++++++++++++++++ .../mixin/fabric/MixinOcelot.java | 45 +++++++++++++++++ .../mixin/fabric/MixinTamableAnimal.java | 39 +++++++++++++++ .../main/resources/architectury.mixins.json | 3 ++ .../event/forge/EventHandlerImplCommon.java | 8 +++ .../architectury/test/events/DebugEvents.java | 4 ++ 7 files changed, 169 insertions(+) create mode 100644 fabric/src/main/java/dev/architectury/mixin/fabric/HorseTameInvoker.java create mode 100644 fabric/src/main/java/dev/architectury/mixin/fabric/MixinOcelot.java create mode 100644 fabric/src/main/java/dev/architectury/mixin/fabric/MixinTamableAnimal.java diff --git a/common/src/main/java/dev/architectury/event/events/common/EntityEvent.java b/common/src/main/java/dev/architectury/event/events/common/EntityEvent.java index ea9bb4ba..402b24e4 100644 --- a/common/src/main/java/dev/architectury/event/events/common/EntityEvent.java +++ b/common/src/main/java/dev/architectury/event/events/common/EntityEvent.java @@ -27,6 +27,8 @@ import net.minecraft.world.damagesource.DamageSource; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.MobSpawnType; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.player.Player; import net.minecraft.world.level.BaseSpawner; import net.minecraft.world.level.Level; import net.minecraft.world.level.LevelAccessor; @@ -53,6 +55,10 @@ public interface EntityEvent { * @see EnterSection#enterSection(Entity, int, int, int, int, int, int) */ Event ENTER_SECTION = EventFactory.createLoop(); + /** + * @see AnimalTame#tame(Animal, Player) + */ + Event ANIMAL_TAME = EventFactory.createEventResult(); interface LivingDeath { /** @@ -130,4 +136,18 @@ public interface EntityEvent { */ void enterSection(Entity entity, int sectionX, int sectionY, int sectionZ, int prevX, int prevY, int prevZ); } + + interface AnimalTame { + /** + * Invoked before a tamable animal is tamed. + * This event only works on vanilla mobs. Mods implementing their own entities may want to make their own events or invoke this. + * Equivalent to Forge's {@code AnimalTameEvent} event. + * + * @param animal The animal being tamed. + * @param player The tamer. + * @return A {@link EventResult} determining the outcome of the event, + * the action may be cancelled by the result. + */ + EventResult tame(Animal animal, Player player); + } } diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/HorseTameInvoker.java b/fabric/src/main/java/dev/architectury/mixin/fabric/HorseTameInvoker.java new file mode 100644 index 00000000..ed4ade42 --- /dev/null +++ b/fabric/src/main/java/dev/architectury/mixin/fabric/HorseTameInvoker.java @@ -0,0 +1,50 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.fabric; + +import dev.architectury.event.events.common.EntityEvent; +import net.minecraft.world.entity.ai.goal.RunAroundLikeCrazyGoal; +import net.minecraft.world.entity.animal.horse.AbstractHorse; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(RunAroundLikeCrazyGoal.class) +public class HorseTameInvoker { + @Shadow + @Final + private AbstractHorse horse; + + @Inject(method = "tick", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/animal/horse/AbstractHorse;tameWithName(Lnet/minecraft/world/entity/player/Player;)Z" + ), cancellable = true + ) + private void tick(CallbackInfo ci) { + if (EntityEvent.ANIMAL_TAME.invoker().tame(this.horse, (Player) this.horse.getPassengers().get(0)).isFalse()) { + ci.cancel(); + } + } +} diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/MixinOcelot.java b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinOcelot.java new file mode 100644 index 00000000..82708af4 --- /dev/null +++ b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinOcelot.java @@ -0,0 +1,45 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.fabric; + +import dev.architectury.event.events.common.EntityEvent; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.InteractionResult; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.animal.Ocelot; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +@Mixin(Ocelot.class) +public class MixinOcelot { + @Inject(method = "mobInteract", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/world/entity/animal/Ocelot;setTrusting(Z)V" + ), cancellable = true) + private void mobInteract(Player player, InteractionHand hand, CallbackInfoReturnable cir) { + if (EntityEvent.ANIMAL_TAME.invoker().tame((Animal) (Object) this, player).isFalse()) { + cir.setReturnValue(InteractionResult.PASS); + } + } +} diff --git a/fabric/src/main/java/dev/architectury/mixin/fabric/MixinTamableAnimal.java b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinTamableAnimal.java new file mode 100644 index 00000000..3748c9a1 --- /dev/null +++ b/fabric/src/main/java/dev/architectury/mixin/fabric/MixinTamableAnimal.java @@ -0,0 +1,39 @@ +/* + * This file is part of architectury. + * Copyright (C) 2020, 2021 architectury + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +package dev.architectury.mixin.fabric; + +import dev.architectury.event.events.common.EntityEvent; +import net.minecraft.world.entity.TamableAnimal; +import net.minecraft.world.entity.animal.Animal; +import net.minecraft.world.entity.player.Player; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(TamableAnimal.class) +public class MixinTamableAnimal { + @Inject(method = "tame", at = @At(value = "HEAD"), cancellable = true) + private void tame(Player player, CallbackInfo ci) { + if (EntityEvent.ANIMAL_TAME.invoker().tame((Animal) (Object) this, player).isFalse()) { + ci.cancel(); + } + } +} diff --git a/fabric/src/main/resources/architectury.mixins.json b/fabric/src/main/resources/architectury.mixins.json index 3c39fac6..a1b2af17 100644 --- a/fabric/src/main/resources/architectury.mixins.json +++ b/fabric/src/main/resources/architectury.mixins.json @@ -20,6 +20,7 @@ ], "mixins": [ "ExplosionPreInvoker", + "HorseTameInvoker", "LivingDeathInvoker", "MixinBaseSpawner", "MixinBlockEntityExtension", @@ -38,6 +39,7 @@ "MixinItemEntity", "MixinLivingEntity", "MixinNaturalSpawner", + "MixinOcelot", "MixinPatrolSpawner", "MixinPersistentEntitySectionManager", "MixinPhantomSpawner", @@ -49,6 +51,7 @@ "MixinServerLevel", "MixinServerPlayer", "MixinServerPlayerGameMode", + "MixinTamableAnimal", "PlayerAttackInvoker" ], "injectors": { diff --git a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java index fa509995..aff44c40 100644 --- a/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java +++ b/forge/src/main/java/me/shedaniel/architectury/event/forge/EventHandlerImplCommon.java @@ -22,6 +22,7 @@ package me.shedaniel.architectury.event.forge; import me.shedaniel.architectury.event.CompoundEventResult; import me.shedaniel.architectury.event.EventResult; import me.shedaniel.architectury.event.events.PlayerEvent; +import me.shedaniel.architectury.event.events.*; import me.shedaniel.architectury.utils.IntValue; import net.minecraft.network.chat.Component; import net.minecraft.server.level.ServerLevel; @@ -41,6 +42,7 @@ import net.minecraftforge.event.TickEvent.WorldTickEvent; import net.minecraftforge.event.entity.EntityEvent.EnteringChunk; import net.minecraftforge.event.entity.EntityJoinWorldEvent; import net.minecraftforge.event.entity.item.ItemTossEvent; +import net.minecraftforge.event.entity.living.AnimalTameEvent; import net.minecraftforge.event.entity.living.LivingAttackEvent; import net.minecraftforge.event.entity.living.LivingDeathEvent; import net.minecraftforge.event.entity.living.LivingSpawnEvent; @@ -255,6 +257,12 @@ public class EventHandlerImplCommon { } } + @SubscribeEvent(priority = EventPriority.HIGH) + public static void event(AnimalTameEvent event) { + InteractionResult result = EntityEvent.ANIMAL_TAMED.invoker().onTame(event.getAnimal(), event.getTamer()); + event.setCanceled(result == InteractionResult.FAIL); + } + @SubscribeEvent(priority = EventPriority.HIGH) public static void event(ItemCraftedEvent event) { PlayerEvent.CRAFT_ITEM.invoker().craft(event.getPlayer(), event.getCrafting(), event.getInventory()); diff --git a/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java b/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java index 4c4405ab..ad4d42c1 100644 --- a/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java +++ b/testmod-common/src/main/java/dev/architectury/test/events/DebugEvents.java @@ -117,6 +117,10 @@ public class DebugEvents { TestMod.SINK.accept(sb.toString()); return EventResult.pass(); })); + EntityEvent.ANIMAL_TAME.register(((animal, player) -> { + TestMod.SINK.accept("%s tamed %s at %s", player.getScoreboardName(), animal.getDisplayName().getString(), toShortString(animal.position())); + return EventResult.pass(); + })); ExplosionEvent.DETONATE.register((world, explosion, affectedEntities) -> { TestMod.SINK.accept(world.dimension().location() + " explodes at " + toShortString(ExplosionHooks.getPosition(explosion)) + logSide(world)); });