From 18c9eaa4cbc22373b022a166c701ca20aac8a9b8 Mon Sep 17 00:00:00 2001
From: Kevin <92656833+kevinthegreat1@users.noreply.github.com>
Date: Wed, 27 Dec 2023 07:59:01 -0500
Subject: [PATCH] Add Client After Block Break Event (#3367)

* Add clientside after block break event

* Update tests

* Checkstyle, of course

* Update Javadoc

* New Event

* Checkstyle 2 electric boogaloo

* Remove block entity parameter

* Refactor ClientPlayerBlockBreakEvents

* Update Javadoc

(cherry picked from commit 389931eb7ae828b9df02d1121079fa5907a709b3)
---
 .../player/ClientPlayerBlockBreakEvents.java  | 59 +++++++++++++++++++
 .../ClientPlayerInteractionManagerMixin.java  | 11 +++-
 .../event/player/PlayerBlockBreakEvents.java  |  8 ++-
 .../interaction/PlayerBreakBlockTests.java    | 12 +---
 .../src/testmod/resources/fabric.mod.json     |  1 +
 .../ClientPlayerBlockBreakTests.java          | 32 ++++++++++
 6 files changed, 111 insertions(+), 12 deletions(-)
 create mode 100644 fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java
 create mode 100644 fabric-events-interaction-v0/src/testmodClient/java/net/fabricmc/fabric/test/client/event/interaction/ClientPlayerBlockBreakTests.java

diff --git a/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java
new file mode 100644
index 000000000..d817088c3
--- /dev/null
+++ b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/api/event/client/player/ClientPlayerBlockBreakEvents.java
@@ -0,0 +1,59 @@
+/*
+ * 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.api.event.client.player;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.client.network.ClientPlayerEntity;
+import net.minecraft.client.world.ClientWorld;
+import net.minecraft.util.math.BlockPos;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+/**
+ * Contains client side events triggered by block breaking.
+ *
+ * <p>For preventing block breaking client side and other purposes, see {@link net.fabricmc.fabric.api.event.player.AttackBlockCallback}.
+ * For server side block break events, see {@link net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents}.
+ */
+public class ClientPlayerBlockBreakEvents {
+	/**
+	 * Callback after a block is broken client side.
+	 *
+	 * <p>Only called client side. For server side see {@link net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents#AFTER}
+	 */
+	public static final Event<After> AFTER = EventFactory.createArrayBacked(After.class,
+			(listeners) -> (world, player, pos, state) -> {
+				for (After event : listeners) {
+					event.afterBlockBreak(world, player, pos, state);
+				}
+			}
+	);
+
+	@FunctionalInterface
+	public interface After {
+		/**
+		 * Called after a block is successfully broken.
+		 *
+		 * @param world  the world where the block was broken
+		 * @param player the player who broke the block
+		 * @param pos    the position where the block was broken
+		 * @param state  the block state <strong>before</strong> the block was broken
+		 */
+		void afterBlockBreak(ClientWorld world, ClientPlayerEntity player, BlockPos pos, BlockState state);
+	}
+}
diff --git a/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/ClientPlayerInteractionManagerMixin.java b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/ClientPlayerInteractionManagerMixin.java
index 272ed6c48..ab3ba8b67 100644
--- a/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/ClientPlayerInteractionManagerMixin.java
+++ b/fabric-events-interaction-v0/src/client/java/net/fabricmc/fabric/mixin/event/interaction/client/ClientPlayerInteractionManagerMixin.java
@@ -24,7 +24,9 @@ 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;
+import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
 
+import net.minecraft.block.BlockState;
 import net.minecraft.client.MinecraftClient;
 import net.minecraft.client.network.ClientPlayNetworkHandler;
 import net.minecraft.client.network.ClientPlayerEntity;
@@ -46,7 +48,9 @@ import net.minecraft.util.hit.BlockHitResult;
 import net.minecraft.util.math.BlockPos;
 import net.minecraft.util.math.Direction;
 import net.minecraft.world.GameMode;
+import net.minecraft.world.World;
 
+import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents;
 import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
 import net.fabricmc.fabric.api.event.player.AttackEntityCallback;
 import net.fabricmc.fabric.api.event.player.UseBlockCallback;
@@ -90,6 +94,11 @@ public abstract class ClientPlayerInteractionManagerMixin {
 		}
 	}
 
+	@Inject(method = "breakBlock", at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;onBroken(Lnet/minecraft/world/WorldAccess;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)V"), locals = LocalCapture.CAPTURE_FAILHARD)
+	private void fabric$onBlockBroken(BlockPos pos, CallbackInfoReturnable<Boolean> cir, World world, BlockState blockState) {
+		ClientPlayerBlockBreakEvents.AFTER.invoker().afterBlockBreak(client.world, client.player, pos, blockState);
+	}
+
 	@Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/client/network/ClientPlayerInteractionManager;sendSequencedPacket(Lnet/minecraft/client/world/ClientWorld;Lnet/minecraft/client/network/SequencedPacketCreator;)V"), method = "interactBlock", cancellable = true)
 	public void interactBlock(ClientPlayerEntity player, Hand hand, BlockHitResult blockHitResult, CallbackInfoReturnable<ActionResult> info) {
 		// hook interactBlock between the world border check and the actual block interaction to invoke the use block event first
@@ -141,5 +150,5 @@ public abstract class ClientPlayerInteractionManagerMixin {
 	}
 
 	@Shadow
-	public abstract void sendSequencedPacket(ClientWorld clientWorld, SequencedPacketCreator supplier);
+	protected abstract void sendSequencedPacket(ClientWorld clientWorld, SequencedPacketCreator supplier);
 }
diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/PlayerBlockBreakEvents.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/PlayerBlockBreakEvents.java
index 7e28b2f1e..c4dad44be 100644
--- a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/PlayerBlockBreakEvents.java
+++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/PlayerBlockBreakEvents.java
@@ -27,6 +27,9 @@ import net.minecraft.world.World;
 import net.fabricmc.fabric.api.event.Event;
 import net.fabricmc.fabric.api.event.EventFactory;
 
+/**
+ * Contains server side events triggered by block breaking.
+ */
 public final class PlayerBlockBreakEvents {
 	private PlayerBlockBreakEvents() { }
 
@@ -53,10 +56,11 @@ public final class PlayerBlockBreakEvents {
 	);
 
 	/**
-	 * Callback after a block is broken.
+	 * Callback after a block is broken server side.
 	 *
-	 * <p>Only called on a logical server.
+	 * <p>Only called on a logical server. For client side see {@link net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents#AFTER}
 	 */
+	@SuppressWarnings("JavadocReference")
 	public static final Event<After> AFTER = EventFactory.createArrayBacked(After.class,
 			(listeners) -> (world, player, pos, state, entity) -> {
 				for (After event : listeners) {
diff --git a/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/PlayerBreakBlockTests.java b/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/PlayerBreakBlockTests.java
index 6a510ec62..dd148f94a 100644
--- a/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/PlayerBreakBlockTests.java
+++ b/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/PlayerBreakBlockTests.java
@@ -29,16 +29,10 @@ public class PlayerBreakBlockTests implements ModInitializer {
 
 	@Override
 	public void onInitialize() {
-		PlayerBlockBreakEvents.BEFORE.register(((world, player, pos, state, entity) -> {
-			return state.getBlock() != Blocks.BEDROCK;
-		}));
+		PlayerBlockBreakEvents.BEFORE.register(((world, player, pos, state, entity) -> state.getBlock() != Blocks.BEDROCK));
 
-		PlayerBlockBreakEvents.CANCELED.register(((world, player, pos, state, entity) -> {
-			LOGGER.info("Block break event canceled at " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ());
-		}));
+		PlayerBlockBreakEvents.CANCELED.register(((world, player, pos, state, entity) -> LOGGER.info("Block break event canceled at {}, {}, {} (client-side = {})", pos.getX(), pos.getY(), pos.getZ(), world.isClient())));
 
-		PlayerBlockBreakEvents.AFTER.register(((world, player, pos, state, entity) -> {
-			LOGGER.info("Block broken at " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ());
-		}));
+		PlayerBlockBreakEvents.AFTER.register(((world, player, pos, state, entity) -> LOGGER.info("Block broken at {}, {}, {} (client-side = {})", pos.getX(), pos.getY(), pos.getZ(), world.isClient())));
 	}
 }
diff --git a/fabric-events-interaction-v0/src/testmod/resources/fabric.mod.json b/fabric-events-interaction-v0/src/testmod/resources/fabric.mod.json
index 1cfc8b579..e2053f214 100644
--- a/fabric-events-interaction-v0/src/testmod/resources/fabric.mod.json
+++ b/fabric-events-interaction-v0/src/testmod/resources/fabric.mod.json
@@ -21,6 +21,7 @@
     ],
     "client": [
       "net.fabricmc.fabric.test.client.event.interaction.ClientPreAttackTests",
+      "net.fabricmc.fabric.test.client.event.interaction.ClientPlayerBlockBreakTests",
       "net.fabricmc.fabric.test.client.event.interaction.PlayerPickBlockTests"
     ]
   }
diff --git a/fabric-events-interaction-v0/src/testmodClient/java/net/fabricmc/fabric/test/client/event/interaction/ClientPlayerBlockBreakTests.java b/fabric-events-interaction-v0/src/testmodClient/java/net/fabricmc/fabric/test/client/event/interaction/ClientPlayerBlockBreakTests.java
new file mode 100644
index 000000000..c980e2779
--- /dev/null
+++ b/fabric-events-interaction-v0/src/testmodClient/java/net/fabricmc/fabric/test/client/event/interaction/ClientPlayerBlockBreakTests.java
@@ -0,0 +1,32 @@
+/*
+ * 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.test.client.event.interaction;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.fabricmc.api.ClientModInitializer;
+import net.fabricmc.fabric.api.event.client.player.ClientPlayerBlockBreakEvents;
+
+public class ClientPlayerBlockBreakTests implements ClientModInitializer {
+	public static final Logger LOGGER = LoggerFactory.getLogger(ClientPlayerBlockBreakTests.class);
+
+	@Override
+	public void onInitializeClient() {
+		ClientPlayerBlockBreakEvents.AFTER.register(((world, player, pos, state) -> LOGGER.info("Block broken at {}, {}, {} (client-side = {})", pos.getX(), pos.getY(), pos.getZ(), world.isClient())));
+	}
+}