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
This commit is contained in:
Kevin 2023-12-27 07:59:01 -05:00 committed by GitHub
parent 06274a4716
commit 389931eb7a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 111 additions and 12 deletions

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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) {

View file

@ -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())));
}
}

View file

@ -19,6 +19,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"
]
}

View file

@ -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())));
}
}