diff --git a/fabric-events-interaction-v0/build.gradle b/fabric-events-interaction-v0/build.gradle
index 4c0b6e03a..e14d99119 100644
--- a/fabric-events-interaction-v0/build.gradle
+++ b/fabric-events-interaction-v0/build.gradle
@@ -1,5 +1,5 @@
archivesBaseName = "fabric-events-interaction-v0"
-version = getSubprojectVersion(project, "0.3.3")
+version = getSubprojectVersion(project, "0.4.0")
dependencies {
compile project(path: ':fabric-api-base', configuration: 'dev')
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
new file mode 100644
index 000000000..2105996d8
--- /dev/null
+++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/api/event/player/PlayerBlockBreakEvents.java
@@ -0,0 +1,123 @@
+/*
+ * 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.player;
+
+import net.minecraft.block.BlockState;
+import net.minecraft.block.entity.BlockEntity;
+import net.minecraft.entity.player.PlayerEntity;
+import net.minecraft.util.math.BlockPos;
+import net.minecraft.world.World;
+
+import net.fabricmc.fabric.api.event.Event;
+import net.fabricmc.fabric.api.event.EventFactory;
+
+public final class PlayerBlockBreakEvents {
+ private PlayerBlockBreakEvents() { }
+
+ /**
+ * Callback before a block is broken.
+ * Only called on the server, however updates are synced with the client.
+ *
+ *
If any listener cancels a block breaking action, that block breaking
+ * action is cancelled and {@link CANCELED} event is fired. Otherwise, the
+ * {@link AFTER} event is fired.
+ */
+ public static final Event BEFORE = EventFactory.createArrayBacked(Before.class,
+ (listeners) -> (world, player, pos, state, entity) -> {
+ for (Before event : listeners) {
+ boolean result = event.beforeBlockBreak(world, player, pos, state, entity);
+
+ if (!result) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ );
+
+ /**
+ * Callback after a block is broken.
+ *
+ * Only called on a logical server.
+ */
+ public static final Event AFTER = EventFactory.createArrayBacked(After.class,
+ (listeners) -> (world, player, pos, state, entity) -> {
+ for (After event : listeners) {
+ event.afterBlockBreak(world, player, pos, state, entity);
+ }
+ }
+ );
+
+ /**
+ * Callback when a block break has been canceled.
+ *
+ * Only called on a logical server. May be used to send packets to revert client-side block changes.
+ */
+ public static final Event CANCELED = EventFactory.createArrayBacked(Canceled.class,
+ (listeners) -> (world, player, pos, state, entity) -> {
+ for (Canceled event : listeners) {
+ event.onBlockBreakCanceled(world, player, pos, state, entity);
+ }
+ }
+ );
+
+ @FunctionalInterface
+ public interface Before {
+ /**
+ * Called before a block is broken and allows cancelling the block breaking.
+ *
+ * Implementations should not modify the world or assume the block break has completed or failed.
+ *
+ * @param world the world in which the block is broken
+ * @param player the player breaking the block
+ * @param pos the position at which the block is broken
+ * @param state the block state before the block is broken
+ * @param blockEntity the block entity before the block is broken, can be {@code null}
+ * @return {@code false} to cancel block breaking action, or {@code true} to pass to next listener
+ */
+ boolean beforeBlockBreak(World world, PlayerEntity player, BlockPos pos, BlockState state, /* Nullable */ BlockEntity blockEntity);
+ }
+
+ @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 before the block was broken
+ * @param blockEntity the block entity of the broken block, can be {@code null}
+ */
+ void afterBlockBreak(World world, PlayerEntity player, BlockPos pos, BlockState state, /* Nullable */ BlockEntity blockEntity);
+ }
+
+ @FunctionalInterface
+ public interface Canceled {
+ /**
+ * Called when a block break has been canceled.
+ *
+ * @param world the world where the block was going to be broken
+ * @param player the player who was going to break the block
+ * @param pos the position where the block was going to be broken
+ * @param state the block state of the block that was going to be broken
+ * @param blockEntity the block entity of the block that was going to be broken, can be {@code null}
+ */
+ void onBlockBreakCanceled(World world, PlayerEntity player, BlockPos pos, BlockState state, /* Nullable */ BlockEntity blockEntity);
+ }
+}
diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/InteractionEventsRouter.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/InteractionEventsRouter.java
index dce144640..6b5d28cc2 100644
--- a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/InteractionEventsRouter.java
+++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/impl/event/interaction/InteractionEventsRouter.java
@@ -16,9 +16,13 @@
package net.fabricmc.fabric.impl.event.interaction;
+import net.minecraft.util.math.BlockPos;
import net.minecraft.block.BlockState;
+import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket;
+import net.minecraft.server.network.ServerPlayerEntity;
import net.minecraft.util.ActionResult;
+import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
import net.fabricmc.api.ModInitializer;
import net.fabricmc.fabric.api.block.BlockAttackInteractionAware;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
@@ -41,5 +45,22 @@ public class InteractionEventsRouter implements ModInitializer {
return ActionResult.PASS;
});
+
+ /*
+ * This code is for telling the client that the block wasn't actually broken.
+ * This covers a 3x3 area due to how vanilla redstone handles updates, as it considers
+ * important functions like quasi-connectivity and redstone dust logic
+ */
+ PlayerBlockBreakEvents.CANCELED.register(((world, player, pos, state, blockEntity) -> {
+ BlockPos cornerPos = pos.add(-1, -1, -1);
+
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ for (int z = 0; z < 3; z++) {
+ ((ServerPlayerEntity) player).networkHandler.sendPacket(new BlockUpdateS2CPacket(world, cornerPos.add(x, y, z)));
+ }
+ }
+ }
+ }));
}
}
diff --git a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/MixinServerPlayerInteractionManager.java b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/MixinServerPlayerInteractionManager.java
index e4a8c041d..f36906997 100644
--- a/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/MixinServerPlayerInteractionManager.java
+++ b/fabric-events-interaction-v0/src/main/java/net/fabricmc/fabric/mixin/event/interaction/MixinServerPlayerInteractionManager.java
@@ -22,7 +22,11 @@ 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.Block;
+import net.minecraft.block.BlockState;
+import net.minecraft.block.entity.BlockEntity;
import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket;
import net.minecraft.item.ItemStack;
import net.minecraft.server.network.ServerPlayerEntity;
@@ -40,6 +44,7 @@ import net.minecraft.world.World;
import net.fabricmc.fabric.api.event.player.AttackBlockCallback;
import net.fabricmc.fabric.api.event.player.UseBlockCallback;
import net.fabricmc.fabric.api.event.player.UseItemCallback;
+import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
@Mixin(ServerPlayerInteractionManager.class)
public class MixinServerPlayerInteractionManager {
@@ -81,4 +86,20 @@ public class MixinServerPlayerInteractionManager {
return;
}
}
+
+ @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;onBreak(Lnet/minecraft/world/World;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;Lnet/minecraft/entity/player/PlayerEntity;)V"), method = "tryBreakBlock", locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
+ private void breakBlock(BlockPos pos, CallbackInfoReturnable cir, BlockState state, BlockEntity entity, Block block) {
+ boolean result = PlayerBlockBreakEvents.BEFORE.invoker().beforeBlockBreak(this.world, this.player, pos, state, entity);
+
+ if (!result) {
+ PlayerBlockBreakEvents.CANCELED.invoker().onBlockBreakCanceled(this.world, this.player, pos, state, entity);
+
+ cir.setReturnValue(false);
+ }
+ }
+
+ @Inject(at = @At(value = "INVOKE", target = "Lnet/minecraft/block/Block;onBroken(Lnet/minecraft/world/WorldAccess;Lnet/minecraft/util/math/BlockPos;Lnet/minecraft/block/BlockState;)V"), method = "tryBreakBlock", locals = LocalCapture.CAPTURE_FAILHARD)
+ private void onBlockBroken(BlockPos pos, CallbackInfoReturnable cir, BlockState state, BlockEntity entity, Block block, boolean b1) {
+ PlayerBlockBreakEvents.AFTER.invoker().afterBlockBreak(this.world, this.player, pos, state, entity);
+ }
}
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
new file mode 100644
index 000000000..402cadc2a
--- /dev/null
+++ b/fabric-events-interaction-v0/src/testmod/java/net/fabricmc/fabric/test/event/interaction/PlayerBreakBlockTests.java
@@ -0,0 +1,44 @@
+/*
+ * 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.event.interaction;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+
+import net.minecraft.block.Blocks;
+
+import net.fabricmc.api.ModInitializer;
+import net.fabricmc.fabric.api.event.player.PlayerBlockBreakEvents;
+
+public class PlayerBreakBlockTests implements ModInitializer {
+ public static final Logger LOGGER = LogManager.getLogger("InteractionEventsTest");
+
+ @Override
+ public void onInitialize() {
+ PlayerBlockBreakEvents.BEFORE.register(((world, player, pos, state, entity) -> {
+ return 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.AFTER.register(((world, player, pos, state, entity) -> {
+ LOGGER.info("Block broken at " + pos.getX() + ", " + pos.getY() + ", " + pos.getZ());
+ }));
+ }
+}
diff --git a/fabric-events-interaction-v0/src/testmod/resources/fabric.mod.json b/fabric-events-interaction-v0/src/testmod/resources/fabric.mod.json
new file mode 100644
index 000000000..fc45679c7
--- /dev/null
+++ b/fabric-events-interaction-v0/src/testmod/resources/fabric.mod.json
@@ -0,0 +1,16 @@
+{
+ "schemaVersion": 1,
+ "id": "fabric-events-interaction-v0-testmod",
+ "name": "Fabric Events Interaction (v0) Test Mod",
+ "version": "1.0.0",
+ "environment": "*",
+ "license": "Apache-2.0",
+ "depends": {
+ "fabric-events-interaction-v0": "*"
+ },
+ "entrypoints": {
+ "main": [
+ "net.fabricmc.fabric.test.event.interaction.PlayerBreakBlockTests"
+ ]
+ }
+}