Minestom/src/main/java/net/minestom/server/listener/PlayerDiggingListener.java

187 lines
8.2 KiB
Java

package net.minestom.server.listener;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.GameMode;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.metadata.PlayerMeta;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.item.ItemUpdateStateEvent;
import net.minestom.server.event.player.PlayerStartDiggingEvent;
import net.minestom.server.event.player.PlayerSwapItemEvent;
import net.minestom.server.instance.Instance;
import net.minestom.server.instance.block.Block;
import net.minestom.server.inventory.PlayerInventory;
import net.minestom.server.item.ItemStack;
import net.minestom.server.item.StackingRule;
import net.minestom.server.network.packet.client.play.ClientPlayerDiggingPacket;
import net.minestom.server.network.packet.server.play.AcknowledgeBlockChangePacket;
import org.jetbrains.annotations.NotNull;
public final class PlayerDiggingListener {
public static void playerDiggingListener(ClientPlayerDiggingPacket packet, Player player) {
final ClientPlayerDiggingPacket.Status status = packet.status();
final Point blockPosition = packet.blockPosition();
final Instance instance = player.getInstance();
if (instance == null) return;
DiggingResult diggingResult = null;
if (status == ClientPlayerDiggingPacket.Status.STARTED_DIGGING) {
if (!instance.isChunkLoaded(blockPosition)) return;
diggingResult = startDigging(player, instance, blockPosition);
} else if (status == ClientPlayerDiggingPacket.Status.CANCELLED_DIGGING) {
if (!instance.isChunkLoaded(blockPosition)) return;
diggingResult = cancelDigging(instance, blockPosition);
} else if (status == ClientPlayerDiggingPacket.Status.FINISHED_DIGGING) {
if (!instance.isChunkLoaded(blockPosition)) return;
diggingResult = finishDigging(player, instance, blockPosition);
} else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM_STACK) {
dropStack(player);
} else if (status == ClientPlayerDiggingPacket.Status.DROP_ITEM) {
dropSingle(player);
} else if (status == ClientPlayerDiggingPacket.Status.UPDATE_ITEM_STATE) {
updateItemState(player);
} else if (status == ClientPlayerDiggingPacket.Status.SWAP_ITEM_HAND) {
swapItemHand(player);
}
// Acknowledge start/cancel/finish digging status
if (diggingResult != null) {
player.sendPacket(new AcknowledgeBlockChangePacket(packet.sequence()));
}
}
private static DiggingResult startDigging(Player player, Instance instance, Point blockPosition) {
final Block block = instance.getBlock(blockPosition);
final GameMode gameMode = player.getGameMode();
// Prevent spectators and check players in adventure mode
if (shouldPreventBreaking(player, block)) {
return new DiggingResult(block, false);
}
if (gameMode == GameMode.CREATIVE) {
return breakBlock(instance, player, blockPosition, block);
}
// Survival digging
// FIXME: verify mineable tag and enchantment
final boolean instantBreak = player.isInstantBreak() || block.registry().hardness() == 0;
if (!instantBreak) {
PlayerStartDiggingEvent playerStartDiggingEvent = new PlayerStartDiggingEvent(player, block, blockPosition);
EventDispatcher.call(playerStartDiggingEvent);
return new DiggingResult(block, !playerStartDiggingEvent.isCancelled());
}
// Client only send a single STARTED_DIGGING when insta-break is enabled
return breakBlock(instance, player, blockPosition, block);
}
private static DiggingResult cancelDigging(Instance instance, Point blockPosition) {
final Block block = instance.getBlock(blockPosition);
return new DiggingResult(block, true);
}
private static DiggingResult finishDigging(Player player, Instance instance, Point blockPosition) {
final Block block = instance.getBlock(blockPosition);
if (shouldPreventBreaking(player, block)) {
return new DiggingResult(block, false);
}
return breakBlock(instance, player, blockPosition, block);
}
private static boolean shouldPreventBreaking(@NotNull Player player, Block block) {
if (player.getGameMode() == GameMode.SPECTATOR) {
// Spectators can't break blocks
return true;
} else if (player.getGameMode() == GameMode.ADVENTURE) {
// Check if the item can break the block with the current item
final ItemStack itemInMainHand = player.getItemInMainHand();
if (!itemInMainHand.meta().canDestroy(block)) {
return true;
}
}
return false;
}
private static void dropStack(Player player) {
final ItemStack droppedItemStack = player.getInventory().getItemInMainHand();
dropItem(player, droppedItemStack, ItemStack.AIR);
}
private static void dropSingle(Player player) {
final ItemStack handItem = player.getInventory().getItemInMainHand();
final StackingRule stackingRule = StackingRule.get();
final int handAmount = stackingRule.getAmount(handItem);
if (handAmount <= 1) {
// Drop the whole item without copy
dropItem(player, handItem, ItemStack.AIR);
} else {
// Drop a single item
dropItem(player,
stackingRule.apply(handItem, 1), // Single dropped item
stackingRule.apply(handItem, handAmount - 1)); // Updated hand
}
}
private static void updateItemState(Player player) {
PlayerMeta meta = player.getEntityMeta();
if (!meta.isHandActive()) return;
Player.Hand hand = meta.getActiveHand();
player.refreshEating(null);
player.triggerStatus((byte) 9);
ItemUpdateStateEvent itemUpdateStateEvent = player.callItemUpdateStateEvent(hand);
if (itemUpdateStateEvent == null) {
player.refreshActiveHand(true, false, false);
} else {
final boolean isOffHand = itemUpdateStateEvent.getHand() == Player.Hand.OFF;
player.refreshActiveHand(itemUpdateStateEvent.hasHandAnimation(), isOffHand, false);
}
}
private static void swapItemHand(Player player) {
final PlayerInventory inventory = player.getInventory();
final ItemStack mainHand = inventory.getItemInMainHand();
final ItemStack offHand = inventory.getItemInOffHand();
PlayerSwapItemEvent swapItemEvent = new PlayerSwapItemEvent(player, offHand, mainHand);
EventDispatcher.callCancellable(swapItemEvent, () -> {
inventory.setItemInMainHand(swapItemEvent.getMainHandItem());
inventory.setItemInOffHand(swapItemEvent.getOffHandItem());
});
}
private static DiggingResult breakBlock(Instance instance,
Player player,
Point blockPosition, Block previousBlock) {
// Unverified block break, client is fully responsible
final boolean success = instance.breakBlock(player, blockPosition);
final Block updatedBlock = instance.getBlock(blockPosition);
if (!success) {
if (previousBlock.isSolid()) {
final Pos playerPosition = player.getPosition();
// Teleport the player back if he broke a solid block just below him
if (playerPosition.sub(0, 1, 0).samePoint(blockPosition)) {
player.teleport(playerPosition);
}
}
}
return new DiggingResult(updatedBlock, success);
}
private static void dropItem(@NotNull Player player,
@NotNull ItemStack droppedItem, @NotNull ItemStack handItem) {
final PlayerInventory playerInventory = player.getInventory();
if (player.dropItem(droppedItem)) {
playerInventory.setItemInMainHand(handItem);
} else {
playerInventory.update();
}
}
private record DiggingResult(Block block, boolean success) {
}
}