From b291a955de266af4998e1f3f7e54d182bcc749e6 Mon Sep 17 00:00:00 2001
From: Adrian Siekierka <kontakt@asie.pl>
Date: Mon, 3 Dec 2018 10:17:07 +0100
Subject: [PATCH] add entity interaction events

---
 .../fabric/events/PlayerInteractionEvent.java | 24 +++++++-
 .../MixinClientPlayerInteractionManager.java  | 39 ++++++++++---
 .../events/MixinServerPlayNetworkHandler.java | 56 +++++++++++++++++++
 .../mixin/events/MixinServerPlayerEntity.java | 48 ++++++++++++++++
 .../MixinServerPlayerInteractionManager.java  |  2 +-
 .../net.fabricmc.fabric.mixins.common.json    |  2 +
 6 files changed, 162 insertions(+), 9 deletions(-)
 create mode 100644 src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayNetworkHandler.java
 create mode 100644 src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerEntity.java

diff --git a/src/main/java/net/fabricmc/fabric/events/PlayerInteractionEvent.java b/src/main/java/net/fabricmc/fabric/events/PlayerInteractionEvent.java
index 1af054161..ada16e512 100644
--- a/src/main/java/net/fabricmc/fabric/events/PlayerInteractionEvent.java
+++ b/src/main/java/net/fabricmc/fabric/events/PlayerInteractionEvent.java
@@ -23,12 +23,15 @@ import net.minecraft.util.ActionResult;
 import net.minecraft.util.Hand;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Facing;
+import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.World;
 
 /**
  * This is a class for INTERACTION EVENTS (think left-clicking/right-clicking). For block placement/break
  * events, look elsewhere - this just handles the interaction!
  *
+ * These hook in BEFORE the spectator checks, so make sure to check for the player's game mode as well!
+ *
  * CURRENT LIMITATIONS:
  *
  * - INTERACT_BLOCK/INTERACT_ITEM do not expect the ItemStack instance in the player's held hand to change!
@@ -40,20 +43,39 @@ public final class PlayerInteractionEvent {
 		ActionResult interact(PlayerEntity player, World world, Hand hand, BlockPos pos, Facing facing);
 	}
 
+	@FunctionalInterface
+	public interface Entity {
+		ActionResult interact(PlayerEntity player, World world, Hand hand, net.minecraft.entity.Entity entity);
+	}
+
 	@FunctionalInterface
 	public interface BlockPositioned {
 		ActionResult interact(PlayerEntity player, World world, Hand hand, BlockPos pos, Facing facing, float hitX, float hitY, float hitZ);
 	}
 
+	@FunctionalInterface
+	public interface EntityPositioned {
+		ActionResult interact(PlayerEntity player, World world, Hand hand, net.minecraft.entity.Entity entity, Vec3d hitPos);
+	}
+
 	@FunctionalInterface
 	public interface Item {
 		ActionResult interact(PlayerEntity player, World world, Hand hand);
 	}
 
-	public static final HandlerRegistry<Block> BREAK_BLOCK = new HandlerList<>();
+	public static final HandlerRegistry<Block> ATTACK_BLOCK = new HandlerList<>();
+	public static final HandlerRegistry<Entity> ATTACK_ENTITY = new HandlerList<>();
+	
+	// TODO: For completeness' sake, but requires us to add a custom packet. Is it worth the complexity?
+	/* public static final HandlerRegistry<Item> ATTACK_ITEM = new HandlerList<>(); */
+
 	public static final HandlerRegistry<BlockPositioned> INTERACT_BLOCK = new HandlerList<>();
+	public static final HandlerRegistry<EntityPositioned> INTERACT_ENTITY_POSITIONED = new HandlerList<>();
 	public static final HandlerRegistry<Item> INTERACT_ITEM = new HandlerList<>();
 
+	@Deprecated
+	public static final HandlerRegistry<Block> BREAK_BLOCK = ATTACK_BLOCK;
+
 	private PlayerInteractionEvent() {
 
 	}
diff --git a/src/main/java/net/fabricmc/fabric/mixin/events/MixinClientPlayerInteractionManager.java b/src/main/java/net/fabricmc/fabric/mixin/events/MixinClientPlayerInteractionManager.java
index 76c7602cd..a1cb04dd1 100644
--- a/src/main/java/net/fabricmc/fabric/mixin/events/MixinClientPlayerInteractionManager.java
+++ b/src/main/java/net/fabricmc/fabric/mixin/events/MixinClientPlayerInteractionManager.java
@@ -22,21 +22,18 @@ import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
 import net.minecraft.client.network.ClientPlayerInteractionManager;
-import net.minecraft.client.network.packet.BlockUpdateClientPacket;
 import net.minecraft.client.world.ClientWorld;
+import net.minecraft.entity.Entity;
 import net.minecraft.entity.player.PlayerEntity;
-import net.minecraft.item.ItemStack;
-import net.minecraft.server.network.ServerPlayerEntity;
-import net.minecraft.server.network.ServerPlayerInteractionManager;
 import net.minecraft.server.network.packet.PlayerInteractBlockServerPacket;
 import net.minecraft.util.ActionResult;
 import net.minecraft.util.Hand;
+import net.minecraft.util.HitResult;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Facing;
 import net.minecraft.util.math.Vec3d;
 import net.minecraft.world.GameMode;
 import net.minecraft.world.World;
-import org.lwjgl.system.CallbackI;
 import org.spongepowered.asm.mixin.Mixin;
 import org.spongepowered.asm.mixin.Shadow;
 import org.spongepowered.asm.mixin.injection.At;
@@ -55,7 +52,7 @@ public class MixinClientPlayerInteractionManager {
 
 	@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/world/GameMode;isCreative()Z", ordinal = 0), method = "attackBlock", cancellable = true)
 	public void attackBlock(BlockPos pos, Facing facing, CallbackInfoReturnable<Boolean> info) {
-		for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.BREAK_BLOCK).getBackingArray()) {
+		for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.ATTACK_BLOCK).getBackingArray()) {
 			PlayerInteractionEvent.Block event = (PlayerInteractionEvent.Block) handler;
 			ActionResult result = event.interact(client.player, client.world, Hand.MAIN, pos, facing);
 			if (result != ActionResult.PASS) {
@@ -72,7 +69,7 @@ public class MixinClientPlayerInteractionManager {
 			return;
 		}
 
-		for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.BREAK_BLOCK).getBackingArray()) {
+		for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.ATTACK_BLOCK).getBackingArray()) {
 			PlayerInteractionEvent.Block event = (PlayerInteractionEvent.Block) handler;
 			ActionResult result = event.interact(client.player, client.world, Hand.MAIN, pos, facing);
 			if (result != ActionResult.PASS) {
@@ -118,4 +115,32 @@ public class MixinClientPlayerInteractionManager {
 			}
 		}
 	}
+
+	@Inject(at = @At("HEAD"), method = "attackEntity", cancellable = true)
+	public void attackEntity(PlayerEntity player, Entity entity, CallbackInfo info) {
+		for (Object handler : ((HandlerList<PlayerInteractionEvent.Entity>) PlayerInteractionEvent.ATTACK_ENTITY).getBackingArray()) {
+			PlayerInteractionEvent.Entity event = (PlayerInteractionEvent.Entity) handler;
+			ActionResult result = event.interact(player, player.getEntityWorld(), Hand.MAIN /* TODO */, entity);
+			if (result != ActionResult.PASS) {
+				info.cancel();
+				return;
+			}
+		}
+	}
+
+	@Inject(at = @At("HEAD"), method = "interactEntityAtLocation", cancellable = true)
+	public void interactEntityAtLocation(PlayerEntity player, Entity entity, HitResult hitResult, Hand hand, CallbackInfoReturnable<ActionResult> info) {
+		// TODO: Remove double Vec3d creation?
+		Vec3d hitVec = new Vec3d(hitResult.pos.x - entity.x, hitResult.pos.y - entity.y, hitResult.pos.z - entity.z);
+
+		for (Object handler : ((HandlerList<PlayerInteractionEvent.EntityPositioned>) PlayerInteractionEvent.INTERACT_ENTITY_POSITIONED).getBackingArray()) {
+			PlayerInteractionEvent.EntityPositioned event = (PlayerInteractionEvent.EntityPositioned) handler;
+			ActionResult result = event.interact(player, player.getEntityWorld(), hand, entity, hitVec);
+			if (result != ActionResult.PASS) {
+				info.setReturnValue(result);
+				info.cancel();
+				return;
+			}
+		}
+	}
 }
diff --git a/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayNetworkHandler.java b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayNetworkHandler.java
new file mode 100644
index 000000000..df76fb8c1
--- /dev/null
+++ b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayNetworkHandler.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.events;
+
+import net.fabricmc.fabric.events.PlayerInteractionEvent;
+import net.fabricmc.fabric.util.HandlerList;
+import net.minecraft.client.network.packet.BlockUpdateClientPacket;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.item.ItemStack;
+import net.minecraft.server.network.ServerPlayNetworkHandler;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.network.ServerPlayerInteractionManager;
+import net.minecraft.server.network.packet.PlayerInteractEntityServerPacket;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Hand;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.util.math.Facing;
+import net.minecraft.world.World;
+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;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
+
+@Mixin(ServerPlayNetworkHandler.class)
+public class MixinServerPlayNetworkHandler {
+	@Shadow
+	public ServerPlayerEntity player;
+
+	@Inject(method = "onPlayerInteractEntity", at = @At(value = "INVOKE", target = "Lnet/minecraft/entity/Entity;interactAt(Lnet/minecraft/entity/player/PlayerEntity;Lnet/minecraft/util/math/Vec3d;Lnet/minecraft/util/Hand;)Lnet/minecraft/util/ActionResult;"), cancellable = true)
+	public void onPlayerInteractEntity(PlayerInteractEntityServerPacket packet, CallbackInfo info) {
+		for (Object handler : ((HandlerList<PlayerInteractionEvent.EntityPositioned>) PlayerInteractionEvent.INTERACT_ENTITY_POSITIONED).getBackingArray()) {
+			PlayerInteractionEvent.EntityPositioned event = (PlayerInteractionEvent.EntityPositioned) handler;
+			ActionResult result = event.interact(player, player.getEntityWorld(), packet.getHand(), packet.getEntity(player.world), packet.getHitPosition());
+			if (result != ActionResult.PASS) {
+				info.cancel();
+				return;
+			}
+		}
+	}
+}
diff --git a/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerEntity.java b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerEntity.java
new file mode 100644
index 000000000..427acbbe4
--- /dev/null
+++ b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerEntity.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (c) 2016, 2017, 2018 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.mixin.events;
+
+import net.fabricmc.fabric.events.PlayerInteractionEvent;
+import net.fabricmc.fabric.util.HandlerList;
+import net.minecraft.entity.Entity;
+import net.minecraft.server.network.ServerPlayNetworkHandler;
+import net.minecraft.server.network.ServerPlayerEntity;
+import net.minecraft.server.network.packet.PlayerInteractEntityServerPacket;
+import net.minecraft.util.ActionResult;
+import net.minecraft.util.Hand;
+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(ServerPlayerEntity.class)
+public class MixinServerPlayerEntity {
+	@Inject(method = "attack", at = @At("HEAD"), cancellable = true)
+	public void onPlayerInteractEntity(Entity target, CallbackInfo info) {
+		ServerPlayerEntity player = (ServerPlayerEntity) (Object) this;
+
+		for (Object handler : ((HandlerList<PlayerInteractionEvent.Entity>) PlayerInteractionEvent.ATTACK_ENTITY).getBackingArray()) {
+			PlayerInteractionEvent.Entity event = (PlayerInteractionEvent.Entity) handler;
+			ActionResult result = event.interact(player, player.getEntityWorld(), Hand.MAIN, target);
+			if (result != ActionResult.PASS) {
+				info.cancel();
+				return;
+			}
+		}
+	}
+}
diff --git a/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerInteractionManager.java b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerInteractionManager.java
index dd73a6e02..e87e95a9e 100644
--- a/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerInteractionManager.java
+++ b/src/main/java/net/fabricmc/fabric/mixin/events/MixinServerPlayerInteractionManager.java
@@ -44,7 +44,7 @@ public class MixinServerPlayerInteractionManager {
 
 	@Inject(at = @At("HEAD"), method = "method_14263", cancellable = true)
 	public void startBlockBreak(BlockPos pos, Facing facing, CallbackInfo info) {
-		for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.BREAK_BLOCK).getBackingArray()) {
+		for (Object handler : ((HandlerList<PlayerInteractionEvent.Block>) PlayerInteractionEvent.ATTACK_BLOCK).getBackingArray()) {
 			PlayerInteractionEvent.Block event = (PlayerInteractionEvent.Block) handler;
 			ActionResult result = event.interact(player, world, Hand.MAIN, pos, facing);
 			if (result != ActionResult.PASS) {
diff --git a/src/main/resources/net.fabricmc.fabric.mixins.common.json b/src/main/resources/net.fabricmc.fabric.mixins.common.json
index 68b0e490d..78b242835 100644
--- a/src/main/resources/net.fabricmc.fabric.mixins.common.json
+++ b/src/main/resources/net.fabricmc.fabric.mixins.common.json
@@ -5,6 +5,8 @@
   "mixins": [
     "commands.MixinServerCommandManager",
     "events.MixinMinecraftServer",
+    "events.MixinServerPlayNetworkHandler",
+    "events.MixinServerPlayerEntity",
     "events.MixinServerPlayerInteractionManager",
     "events.MixinWorld",
     "helpers.MixinBlock",