From e91849a8359185679cc84ea0beb1e7029bd40ca8 Mon Sep 17 00:00:00 2001
From: Jonathan Coates <git@squiddev.cc>
Date: Tue, 18 Jul 2023 12:54:27 +0100
Subject: [PATCH] Fix crash when beehive is broken by fake player (#3190)

* Fix crash when beehive is broken by fake player

When a beehive is broken, every nearby bee targets a random player.
However, if there are no nearby players, the game crashes.

This should not occur under normal (vanilla) conditions. However, if a
beehive is broken by a fake player there are no players in range, and so
we see a crash.

* Checkstyle, my beloved

* Remove public modifier

* See see see
---
 .../event/interaction/BeehiveBlockMixin.java  | 50 +++++++++++++++++++
 .../fabric-events-interaction-v0.mixins.json  |  1 +
 .../event/interaction/FakePlayerTests.java    | 21 ++++++++
 3 files changed, 72 insertions(+)
 create mode 100644 fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/BeehiveBlockMixin.java

diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/BeehiveBlockMixin.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/BeehiveBlockMixin.java
new file mode 100644
index 000000000..e8ed4be49
--- /dev/null
+++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/BeehiveBlockMixin.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 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.event.interaction;
+
+import java.util.List;
+
+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;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
+
+import net.minecraft.block.BeehiveBlock;
+import net.minecraft.entity.passive.BeeEntity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+@Mixin(BeehiveBlock.class)
+class BeehiveBlockMixin {
+	@Inject(
+			method = "angerNearbyBees",
+			cancellable = true,
+			at = @At(
+					value = "INVOKE_ASSIGN",
+					target = "Lnet/minecraft/world/World;getNonSpectatingEntities(Ljava/lang/Class;Lnet/minecraft/util/math/Box;)Ljava/util/List;",
+					ordinal = 1 // Only capture the PlayerEntity call.
+			),
+			locals = LocalCapture.CAPTURE_FAILHARD
+	)
+	private void afterNearbyBeesPlayers(World world, BlockPos pos, CallbackInfo ci, List<BeeEntity> bees, List<PlayerEntity> players) {
+		// If a fake player broke the beehive, there will be no nearby players. This causes a crash later on as we try
+		// to pick a random player - we early return to avoid this.
+		if (players.isEmpty()) ci.cancel();
+	}
+}
diff --git a/fabric-events-interaction-v0/src/main/resources/fabric-events-interaction-v0.mixins.json b/fabric-events-interaction-v0/src/main/resources/fabric-events-interaction-v0.mixins.json
index 9156d082b..806c5161d 100644
--- a/fabric-events-interaction-v0/src/main/resources/fabric-events-interaction-v0.mixins.json
+++ b/fabric-events-interaction-v0/src/main/resources/fabric-events-interaction-v0.mixins.json
@@ -3,6 +3,7 @@
   "package": "net.fabricmc.fabric.mixin.event.interaction",
   "compatibilityLevel": "JAVA_16",
   "mixins": [
+    "BeehiveBlockMixin",
     "PlayerAdvancementTrackerMixin",
     "ServerPlayerEntityMixin",
     "ServerPlayerInteractionManagerMixin",
diff --git a/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java b/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java
index 5917e8dca..2bda646cc 100644
--- a/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java
+++ b/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/FakePlayerTests.java
@@ -17,10 +17,12 @@
 package net.fabricmc.fabric.test.event.interaction;
 
 import net.minecraft.block.Blocks;
+import net.minecraft.entity.EntityType;
 import net.minecraft.entity.player.PlayerEntity;
 import net.minecraft.item.ItemStack;
 import net.minecraft.item.ItemUsageContext;
 import net.minecraft.item.Items;
+import net.minecraft.server.network.ServerPlayerEntity;
 import net.minecraft.test.GameTest;
 import net.minecraft.test.TestContext;
 import net.minecraft.util.Hand;
@@ -60,4 +62,23 @@ public class FakePlayerTests {
 		context.assertTrue(signStack.isEmpty(), "Sign stack was not emptied");
 		context.complete();
 	}
+
+	/**
+	 * Try breaking a beehive with a fake player (see {@code BeehiveBlockMixin}).
+	 */
+	@GameTest(templateName = FabricGameTest.EMPTY_STRUCTURE)
+	public void testFakePlayerBreakBeehive(TestContext context) {
+		BlockPos basePos = new BlockPos(0, 1, 0);
+		context.setBlockState(basePos, Blocks.BEEHIVE);
+		context.spawnEntity(EntityType.BEE, basePos.up());
+
+		ServerPlayerEntity fakePlayer = FakePlayer.get(context.getWorld());
+
+		BlockPos fakePlayerPos = context.getAbsolutePos(basePos.add(2, 0, 2));
+		fakePlayer.setPosition(fakePlayerPos.getX(), fakePlayerPos.getY(), fakePlayerPos.getZ());
+
+		context.assertTrue(fakePlayer.interactionManager.tryBreakBlock(context.getAbsolutePos(basePos)), "Block was not broken");
+		context.expectBlock(Blocks.AIR, basePos);
+		context.complete();
+	}
 }