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" + ] + } +}