aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-spigotflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-spigotflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch')
-rw-r--r--patch-remap/mache-spigotflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch669
1 files changed, 669 insertions, 0 deletions
diff --git a/patch-remap/mache-spigotflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/patch-remap/mache-spigotflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch
new file mode 100644
index 0000000000..78a8dc985d
--- /dev/null
+++ b/patch-remap/mache-spigotflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -0,0 +1,669 @@
+--- a/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -9,22 +9,42 @@
+ import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket;
+ import net.minecraft.network.protocol.game.ClientboundPlayerInfoUpdatePacket;
+ import net.minecraft.network.protocol.game.ServerboundPlayerActionPacket;
+-import net.minecraft.server.network.ServerGamePacketListenerImpl;
+-import net.minecraft.world.InteractionHand;
++import net.minecraft.world.EnumHand;
++import net.minecraft.world.ITileInventory;
+ import net.minecraft.world.InteractionResult;
+ import net.minecraft.world.InteractionResultHolder;
+-import net.minecraft.world.MenuProvider;
++import net.minecraft.world.entity.EquipmentSlot;
++import net.minecraft.world.item.DoubleHighBlockItem;
+ import net.minecraft.world.item.ItemStack;
+ import net.minecraft.world.item.context.UseOnContext;
+ import net.minecraft.world.level.GameType;
+ import net.minecraft.world.level.Level;
+ import net.minecraft.world.level.block.Block;
+ import net.minecraft.world.level.block.GameMasterBlock;
++import net.minecraft.world.level.block.TrapDoorBlock;
+ import net.minecraft.world.level.block.entity.BlockEntity;
+-import net.minecraft.world.level.block.state.BlockState;
++import net.minecraft.world.level.block.state.IBlockData;
++import org.slf4j.Logger;
++
++// CraftBukkit start
++import java.util.ArrayList;
++import net.minecraft.server.MinecraftServer;
++import net.minecraft.server.network.ServerGamePacketListenerImpl;
++import net.minecraft.world.level.block.Blocks;
++import net.minecraft.world.level.block.CakeBlock;
++import net.minecraft.world.level.block.DoorBlock;
++import net.minecraft.world.level.block.state.properties.BlockPropertyDoubleBlockHalf;
+ import net.minecraft.world.phys.BlockHitResult;
+ import net.minecraft.world.phys.Vec3;
+-import org.slf4j.Logger;
++import org.bukkit.GameMode;
++import org.bukkit.craftbukkit.block.CraftBlock;
++import org.bukkit.event.block.BlockBreakEvent;
++import org.bukkit.craftbukkit.event.CraftEventFactory;
++import org.bukkit.event.Event;
++import org.bukkit.event.block.Action;
++import org.bukkit.event.player.PlayerGameModeChangeEvent;
++import org.bukkit.event.player.PlayerInteractEvent;
++// CraftBukkit end
+
+ public class ServerPlayerGameMode {
+
+@@ -43,31 +63,38 @@
+ private int delayedTickStart;
+ private int lastSentState;
+
+- public ServerPlayerGameMode(ServerPlayer serverplayer) {
++ public ServerPlayerGameMode(ServerPlayer player) {
+ this.gameModeForPlayer = GameType.DEFAULT_MODE;
+ this.destroyPos = BlockPos.ZERO;
+ this.delayedDestroyPos = BlockPos.ZERO;
+ this.lastSentState = -1;
+- this.player = serverplayer;
+- this.level = serverplayer.serverLevel();
++ this.player = player;
++ this.level = player.serverLevel();
+ }
+
+- public boolean changeGameModeForPlayer(GameType gametype) {
+- if (gametype == this.gameModeForPlayer) {
++ public boolean changeGameModeForPlayer(GameType gameModeForPlayer) {
++ if (gameModeForPlayer == this.gameModeForPlayer) {
+ return false;
+ } else {
+- this.setGameModeForPlayer(gametype, this.previousGameModeForPlayer);
++ // CraftBukkit start
++ PlayerGameModeChangeEvent event = new PlayerGameModeChangeEvent(player.getBukkitEntity(), GameMode.getByValue(gameModeForPlayer.getId()));
++ level.getCraftServer().getPluginManager().callEvent(event);
++ if (event.isCancelled()) {
++ return false;
++ }
++ // CraftBukkit end
++ this.setGameModeForPlayer(gameModeForPlayer, this.previousGameModeForPlayer);
+ this.player.onUpdateAbilities();
+- this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player));
++ this.player.server.getPlayerList().broadcastAll(new ClientboundPlayerInfoUpdatePacket(ClientboundPlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, this.player), this.player); // CraftBukkit
+ this.level.updateSleepingPlayerList();
+ return true;
+ }
+ }
+
+- protected void setGameModeForPlayer(GameType gametype, @Nullable GameType gametype1) {
+- this.previousGameModeForPlayer = gametype1;
+- this.gameModeForPlayer = gametype;
+- gametype.updatePlayerAbilities(this.player.getAbilities());
++ protected void setGameModeForPlayer(GameType gameModeForPlayer, @Nullable GameType previousGameModeForPlayer) {
++ this.previousGameModeForPlayer = previousGameModeForPlayer;
++ this.gameModeForPlayer = gameModeForPlayer;
++ gameModeForPlayer.updatePlayerAbilities(this.player.getAbilities());
+ }
+
+ public GameType getGameModeForPlayer() {
+@@ -88,15 +115,15 @@
+ }
+
+ public void tick() {
+- ++this.gameTicks;
+- BlockState blockstate;
++ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
++ IBlockData iblockdata;
+
+ if (this.hasDelayedDestroy) {
+- blockstate = this.level.getBlockState(this.delayedDestroyPos);
+- if (blockstate.isAir()) {
++ iblockdata = this.level.getBlockState(this.delayedDestroyPos);
++ if (iblockdata.isAir()) {
+ this.hasDelayedDestroy = false;
+ } else {
+- float f = this.incrementDestroyProgress(blockstate, this.delayedDestroyPos, this.delayedTickStart);
++ float f = this.incrementDestroyProgress(iblockdata, this.delayedDestroyPos, this.delayedTickStart);
+
+ if (f >= 1.0F) {
+ this.hasDelayedDestroy = false;
+@@ -104,193 +131,321 @@
+ }
+ }
+ } else if (this.isDestroyingBlock) {
+- blockstate = this.level.getBlockState(this.destroyPos);
+- if (blockstate.isAir()) {
++ iblockdata = this.level.getBlockState(this.destroyPos);
++ if (iblockdata.isAir()) {
+ this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
+ this.lastSentState = -1;
+ this.isDestroyingBlock = false;
+ } else {
+- this.incrementDestroyProgress(blockstate, this.destroyPos, this.destroyProgressStart);
++ this.incrementDestroyProgress(iblockdata, this.destroyPos, this.destroyProgressStart);
+ }
+ }
+
+ }
+
+- private float incrementDestroyProgress(BlockState blockstate, BlockPos blockpos, int i) {
+- int j = this.gameTicks - i;
+- float f = blockstate.getDestroyProgress(this.player, this.player.level(), blockpos) * (float) (j + 1);
++ private float incrementDestroyProgress(IBlockData state, BlockPos pos, int startTick) {
++ int j = this.gameTicks - startTick;
++ float f = state.getDestroyProgress(this.player, this.player.level(), pos) * (float) (j + 1);
+ int k = (int) (f * 10.0F);
+
+ if (k != this.lastSentState) {
+- this.level.destroyBlockProgress(this.player.getId(), blockpos, k);
++ this.level.destroyBlockProgress(this.player.getId(), pos, k);
+ this.lastSentState = k;
+ }
+
+ return f;
+ }
+
+- private void debugLogging(BlockPos blockpos, boolean flag, int i, String s) {}
++ private void debugLogging(BlockPos pos, boolean flag, int sequence, String message) {}
+
+- public void handleBlockBreakAction(BlockPos blockpos, ServerboundPlayerActionPacket.Action serverboundplayeractionpacket_action, Direction direction, int i, int j) {
+- if (this.player.getEyePosition().distanceToSqr(Vec3.atCenterOf(blockpos)) > ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) {
+- this.debugLogging(blockpos, false, j, "too far");
+- } else if (blockpos.getY() >= i) {
+- this.player.connection.send(new ClientboundBlockUpdatePacket(blockpos, this.level.getBlockState(blockpos)));
+- this.debugLogging(blockpos, false, j, "too high");
++ public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.EnumPlayerDigType action, Direction face, int maxBuildHeight, int sequence) {
++ if (this.player.getEyePosition().distanceToSqr(Vec3.atCenterOf(pos)) > ServerGamePacketListenerImpl.MAX_INTERACTION_DISTANCE) {
++ this.debugLogging(pos, false, sequence, "too far");
++ } else if (pos.getY() >= maxBuildHeight) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
++ this.debugLogging(pos, false, sequence, "too high");
+ } else {
+- BlockState blockstate;
++ IBlockData iblockdata;
+
+- if (serverboundplayeractionpacket_action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
+- if (!this.level.mayInteract(this.player, blockpos)) {
+- this.player.connection.send(new ClientboundBlockUpdatePacket(blockpos, this.level.getBlockState(blockpos)));
+- this.debugLogging(blockpos, false, j, "may not interact");
++ if (action == ServerboundPlayerActionPacket.EnumPlayerDigType.START_DESTROY_BLOCK) {
++ if (!this.level.mayInteract(this.player, pos)) {
++ // CraftBukkit start - fire PlayerInteractEvent
++ CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), EnumHand.MAIN_HAND);
++ this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
++ this.debugLogging(pos, false, sequence, "may not interact");
++ // Update any tile entity data for this block
++ BlockEntity tileentity = level.getBlockEntity(pos);
++ if (tileentity != null) {
++ this.player.connection.send(tileentity.getUpdatePacket());
++ }
++ // CraftBukkit end
+ return;
+ }
+
++ // CraftBukkit start
++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(this.player, Action.LEFT_CLICK_BLOCK, pos, face, this.player.getInventory().getSelected(), EnumHand.MAIN_HAND);
++ if (event.isCancelled()) {
++ // Let the client know the block still exists
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ // Update any tile entity data for this block
++ BlockEntity tileentity = this.level.getBlockEntity(pos);
++ if (tileentity != null) {
++ this.player.connection.send(tileentity.getUpdatePacket());
++ }
++ return;
++ }
++ // CraftBukkit end
++
+ if (this.isCreative()) {
+- this.destroyAndAck(blockpos, j, "creative destroy");
++ this.destroyAndAck(pos, sequence, "creative destroy");
+ return;
+ }
+
+- if (this.player.blockActionRestricted(this.level, blockpos, this.gameModeForPlayer)) {
+- this.player.connection.send(new ClientboundBlockUpdatePacket(blockpos, this.level.getBlockState(blockpos)));
+- this.debugLogging(blockpos, false, j, "block action restricted");
++ if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
++ this.debugLogging(pos, false, sequence, "block action restricted");
+ return;
+ }
+
+ this.destroyProgressStart = this.gameTicks;
+ float f = 1.0F;
+
+- blockstate = this.level.getBlockState(blockpos);
+- if (!blockstate.isAir()) {
+- blockstate.attack(this.level, blockpos, this.player);
+- f = blockstate.getDestroyProgress(this.player, this.player.level(), blockpos);
++ iblockdata = this.level.getBlockState(pos);
++ // CraftBukkit start - Swings at air do *NOT* exist.
++ if (event.useInteractedBlock() == Event.Result.DENY) {
++ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
++ IBlockData data = this.level.getBlockState(pos);
++ if (data.getBlock() instanceof DoorBlock) {
++ // For some reason *BOTH* the bottom/top part have to be marked updated.
++ boolean bottom = data.getValue(DoorBlock.HALF) == BlockPropertyDoubleBlockHalf.LOWER;
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, bottom ? pos.above() : pos.below()));
++ } else if (data.getBlock() instanceof TrapDoorBlock) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ }
++ } else if (!iblockdata.isAir()) {
++ iblockdata.attack(this.level, pos, this.player);
++ f = iblockdata.getDestroyProgress(this.player, this.player.level(), pos);
+ }
+
+- if (!blockstate.isAir() && f >= 1.0F) {
+- this.destroyAndAck(blockpos, j, "insta mine");
++ if (event.useItemInHand() == Event.Result.DENY) {
++ // If we 'insta destroyed' then the client needs to be informed.
++ if (f > 1.0f) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ }
++ return;
++ }
++ org.bukkit.event.block.BlockDamageEvent blockEvent = CraftEventFactory.callBlockDamageEvent(this.player, pos, this.player.getInventory().getSelected(), f >= 1.0f);
++
++ if (blockEvent.isCancelled()) {
++ // Let the client know the block still exists
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++ return;
++ }
++
++ if (blockEvent.getInstaBreak()) {
++ f = 2.0f;
++ }
++ // CraftBukkit end
++
++ if (!iblockdata.isAir() && f >= 1.0F) {
++ this.destroyAndAck(pos, sequence, "insta mine");
+ } else {
+ if (this.isDestroyingBlock) {
+ this.player.connection.send(new ClientboundBlockUpdatePacket(this.destroyPos, this.level.getBlockState(this.destroyPos)));
+- this.debugLogging(blockpos, false, j, "abort destroying since another started (client insta mine, server disagreed)");
++ this.debugLogging(pos, false, sequence, "abort destroying since another started (client insta mine, server disagreed)");
+ }
+
+ this.isDestroyingBlock = true;
+- this.destroyPos = blockpos.immutable();
++ this.destroyPos = pos.immutable();
+ int k = (int) (f * 10.0F);
+
+- this.level.destroyBlockProgress(this.player.getId(), blockpos, k);
+- this.debugLogging(blockpos, true, j, "actual start of destroying");
++ this.level.destroyBlockProgress(this.player.getId(), pos, k);
++ this.debugLogging(pos, true, sequence, "actual start of destroying");
+ this.lastSentState = k;
+ }
+- } else if (serverboundplayeractionpacket_action == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
+- if (blockpos.equals(this.destroyPos)) {
++ } else if (action == ServerboundPlayerActionPacket.EnumPlayerDigType.STOP_DESTROY_BLOCK) {
++ if (pos.equals(this.destroyPos)) {
+ int l = this.gameTicks - this.destroyProgressStart;
+
+- blockstate = this.level.getBlockState(blockpos);
+- if (!blockstate.isAir()) {
+- float f1 = blockstate.getDestroyProgress(this.player, this.player.level(), blockpos) * (float) (l + 1);
++ iblockdata = this.level.getBlockState(pos);
++ if (!iblockdata.isAir()) {
++ float f1 = iblockdata.getDestroyProgress(this.player, this.player.level(), pos) * (float) (l + 1);
+
+ if (f1 >= 0.7F) {
+ this.isDestroyingBlock = false;
+- this.level.destroyBlockProgress(this.player.getId(), blockpos, -1);
+- this.destroyAndAck(blockpos, j, "destroyed");
++ this.level.destroyBlockProgress(this.player.getId(), pos, -1);
++ this.destroyAndAck(pos, sequence, "destroyed");
+ return;
+ }
+
+ if (!this.hasDelayedDestroy) {
+ this.isDestroyingBlock = false;
+ this.hasDelayedDestroy = true;
+- this.delayedDestroyPos = blockpos;
++ this.delayedDestroyPos = pos;
+ this.delayedTickStart = this.destroyProgressStart;
+ }
+ }
+ }
+
+- this.debugLogging(blockpos, true, j, "stopped destroying");
+- } else if (serverboundplayeractionpacket_action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
++ this.debugLogging(pos, true, sequence, "stopped destroying");
++ } else if (action == ServerboundPlayerActionPacket.EnumPlayerDigType.ABORT_DESTROY_BLOCK) {
+ this.isDestroyingBlock = false;
+- if (!Objects.equals(this.destroyPos, blockpos)) {
+- ServerPlayerGameMode.LOGGER.warn("Mismatch in destroy block pos: {} {}", this.destroyPos, blockpos);
++ if (!Objects.equals(this.destroyPos, pos)) {
++ ServerPlayerGameMode.LOGGER.debug("Mismatch in destroy block pos: {} {}", this.destroyPos, pos); // CraftBukkit - SPIGOT-5457 sent by client when interact event cancelled
+ this.level.destroyBlockProgress(this.player.getId(), this.destroyPos, -1);
+- this.debugLogging(blockpos, true, j, "aborted mismatched destroying");
++ this.debugLogging(pos, true, sequence, "aborted mismatched destroying");
+ }
+
+- this.level.destroyBlockProgress(this.player.getId(), blockpos, -1);
+- this.debugLogging(blockpos, true, j, "aborted destroying");
++ this.level.destroyBlockProgress(this.player.getId(), pos, -1);
++ this.debugLogging(pos, true, sequence, "aborted destroying");
++
++ CraftEventFactory.callBlockDamageAbortEvent(this.player, pos, this.player.getInventory().getSelected()); // CraftBukkit
+ }
+
+ }
+ }
+
+- public void destroyAndAck(BlockPos blockpos, int i, String s) {
+- if (this.destroyBlock(blockpos)) {
+- this.debugLogging(blockpos, true, i, s);
++ public void destroyAndAck(BlockPos pos, int sequence, String message) {
++ if (this.destroyBlock(pos)) {
++ this.debugLogging(pos, true, sequence, message);
+ } else {
+- this.player.connection.send(new ClientboundBlockUpdatePacket(blockpos, this.level.getBlockState(blockpos)));
+- this.debugLogging(blockpos, false, i, s);
++ this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
++ this.debugLogging(pos, false, sequence, message);
+ }
+
+ }
+
+- public boolean destroyBlock(BlockPos blockpos) {
+- BlockState blockstate = this.level.getBlockState(blockpos);
++ public boolean destroyBlock(BlockPos pos) {
++ IBlockData iblockdata = this.level.getBlockState(pos);
++ // CraftBukkit start - fire BlockBreakEvent
++ org.bukkit.block.Block bblock = CraftBlock.at(level, pos);
++ BlockBreakEvent event = null;
+
+- if (!this.player.getMainHandItem().getItem().canAttackBlock(blockstate, this.level, blockpos, this.player)) {
++ if (this.player instanceof ServerPlayer) {
++ // Sword + Creative mode pre-cancel
++ boolean isSwordNoBreak = !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player);
++
++ // Tell client the block is gone immediately then process events
++ // Don't tell the client if its a creative sword break because its not broken!
++ if (level.getBlockEntity(pos) == null && !isSwordNoBreak) {
++ ClientboundBlockUpdatePacket packet = new ClientboundBlockUpdatePacket(pos, Blocks.AIR.defaultBlockState());
++ this.player.connection.send(packet);
++ }
++
++ event = new BlockBreakEvent(bblock, this.player.getBukkitEntity());
++
++ // Sword + Creative mode pre-cancel
++ event.setCancelled(isSwordNoBreak);
++
++ // Calculate default block experience
++ IBlockData nmsData = this.level.getBlockState(pos);
++ Block nmsBlock = nmsData.getBlock();
++
++ ItemStack itemstack = this.player.getItemBySlot(EquipmentSlot.MAINHAND);
++
++ if (nmsBlock != null && !event.isCancelled() && !this.isCreative() && this.player.hasCorrectToolForDrops(nmsBlock.defaultBlockState())) {
++ event.setExpToDrop(nmsBlock.getExpDrop(nmsData, this.level, pos, itemstack, true));
++ }
++
++ this.level.getCraftServer().getPluginManager().callEvent(event);
++
++ if (event.isCancelled()) {
++ if (isSwordNoBreak) {
++ return false;
++ }
++ // Let the client know the block still exists
++ this.player.connection.send(new ClientboundBlockUpdatePacket(this.level, pos));
++
++ // Brute force all possible updates
++ for (Direction dir : Direction.values()) {
++ this.player.connection.send(new ClientboundBlockUpdatePacket(level, pos.relative(dir)));
++ }
++
++ // Update any tile entity data for this block
++ BlockEntity tileentity = this.level.getBlockEntity(pos);
++ if (tileentity != null) {
++ this.player.connection.send(tileentity.getUpdatePacket());
++ }
++ return false;
++ }
++ }
++ // CraftBukkit end
++
++ if (false && !this.player.getMainHandItem().getItem().canAttackBlock(iblockdata, this.level, pos, this.player)) { // CraftBukkit - false
+ return false;
+ } else {
+- BlockEntity blockentity = this.level.getBlockEntity(blockpos);
+- Block block = blockstate.getBlock();
++ iblockdata = this.level.getBlockState(pos); // CraftBukkit - update state from plugins
++ if (iblockdata.isAir()) return false; // CraftBukkit - A plugin set block to air without cancelling
++ BlockEntity tileentity = this.level.getBlockEntity(pos);
++ Block block = iblockdata.getBlock();
+
+ if (block instanceof GameMasterBlock && !this.player.canUseGameMasterBlocks()) {
+- this.level.sendBlockUpdated(blockpos, blockstate, blockstate, 3);
++ this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3);
+ return false;
+- } else if (this.player.blockActionRestricted(this.level, blockpos, this.gameModeForPlayer)) {
++ } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
+ return false;
+ } else {
+- BlockState blockstate1 = block.playerWillDestroy(this.level, blockpos, blockstate, this.player);
+- boolean flag = this.level.removeBlock(blockpos, false);
++ // CraftBukkit start
++ org.bukkit.block.BlockState state = bblock.getState();
++ level.captureDrops = new ArrayList<>();
++ // CraftBukkit end
++ IBlockData iblockdata1 = block.playerWillDestroy(this.level, pos, iblockdata, this.player);
++ boolean flag = this.level.removeBlock(pos, false);
+
+ if (flag) {
+- block.destroy(this.level, blockpos, blockstate1);
++ block.destroy(this.level, pos, iblockdata1);
+ }
+
+ if (this.isCreative()) {
+- return true;
++ // return true; // CraftBukkit
+ } else {
+ ItemStack itemstack = this.player.getMainHandItem();
+ ItemStack itemstack1 = itemstack.copy();
+- boolean flag1 = this.player.hasCorrectToolForDrops(blockstate1);
++ boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
+
+- itemstack.mineBlock(this.level, blockstate1, blockpos, this.player);
+- if (flag && flag1) {
+- block.playerDestroy(this.level, this.player, blockpos, blockstate1, blockentity, itemstack1);
++ itemstack.mineBlock(this.level, iblockdata1, pos, this.player);
++ if (flag && flag1 && event.isDropItems()) { // CraftBukkit - Check if block should drop items
++ block.playerDestroy(this.level, this.player, pos, iblockdata1, tileentity, itemstack1);
+ }
+
+- return true;
++ // return true; // CraftBukkit
+ }
++ // CraftBukkit start
++ if (event.isDropItems()) {
++ org.bukkit.craftbukkit.event.CraftEventFactory.handleBlockDropItemEvent(bblock, state, this.player, level.captureDrops);
++ }
++ level.captureDrops = null;
++
++ // Drop event experience
++ if (flag && event != null) {
++ iblockdata.getBlock().popExperience(this.level, pos, event.getExpToDrop());
++ }
++
++ return true;
++ // CraftBukkit end
+ }
+ }
+ }
+
+- public InteractionResult useItem(ServerPlayer serverplayer, Level level, ItemStack itemstack, InteractionHand interactionhand) {
++ public InteractionResult useItem(ServerPlayer player, Level level, ItemStack stack, EnumHand hand) {
+ if (this.gameModeForPlayer == GameType.SPECTATOR) {
+ return InteractionResult.PASS;
+- } else if (serverplayer.getCooldowns().isOnCooldown(itemstack.getItem())) {
++ } else if (player.getCooldowns().isOnCooldown(stack.getItem())) {
+ return InteractionResult.PASS;
+ } else {
+- int i = itemstack.getCount();
+- int j = itemstack.getDamageValue();
+- InteractionResultHolder<ItemStack> interactionresultholder = itemstack.use(level, serverplayer, interactionhand);
+- ItemStack itemstack1 = (ItemStack) interactionresultholder.getObject();
++ int i = stack.getCount();
++ int j = stack.getDamageValue();
++ InteractionResultHolder<ItemStack> interactionresultwrapper = stack.use(level, player, hand);
++ ItemStack itemstack1 = (ItemStack) interactionresultwrapper.getObject();
+
+- if (itemstack1 == itemstack && itemstack1.getCount() == i && itemstack1.getUseDuration() <= 0 && itemstack1.getDamageValue() == j) {
+- return interactionresultholder.getResult();
+- } else if (interactionresultholder.getResult() == InteractionResult.FAIL && itemstack1.getUseDuration() > 0 && !serverplayer.isUsingItem()) {
+- return interactionresultholder.getResult();
++ if (itemstack1 == stack && itemstack1.getCount() == i && itemstack1.getUseDuration() <= 0 && itemstack1.getDamageValue() == j) {
++ return interactionresultwrapper.getResult();
++ } else if (interactionresultwrapper.getResult() == InteractionResult.FAIL && itemstack1.getUseDuration() > 0 && !player.isUsingItem()) {
++ return interactionresultwrapper.getResult();
+ } else {
+- if (itemstack != itemstack1) {
+- serverplayer.setItemInHand(interactionhand, itemstack1);
++ if (stack != itemstack1) {
++ player.setItemInHand(hand, itemstack1);
+ }
+
+ if (this.isCreative() && itemstack1 != ItemStack.EMPTY) {
+@@ -301,72 +456,112 @@
+ }
+
+ if (itemstack1.isEmpty()) {
+- serverplayer.setItemInHand(interactionhand, ItemStack.EMPTY);
++ player.setItemInHand(hand, ItemStack.EMPTY);
+ }
+
+- if (!serverplayer.isUsingItem()) {
+- serverplayer.inventoryMenu.sendAllDataToRemote();
++ if (!player.isUsingItem()) {
++ player.inventoryMenu.sendAllDataToRemote();
+ }
+
+- return interactionresultholder.getResult();
++ return interactionresultwrapper.getResult();
+ }
+ }
+ }
+
+- public InteractionResult useItemOn(ServerPlayer serverplayer, Level level, ItemStack itemstack, InteractionHand interactionhand, BlockHitResult blockhitresult) {
+- BlockPos blockpos = blockhitresult.getBlockPos();
+- BlockState blockstate = level.getBlockState(blockpos);
++ // CraftBukkit start - whole method
++ public boolean interactResult = false;
++ public boolean firedInteract = false;
++ public BlockPos interactPosition;
++ public EnumHand interactHand;
++ public ItemStack interactItemStack;
++ public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, EnumHand hand, BlockHitResult hitResult) {
++ BlockPos blockposition = hitResult.getBlockPos();
++ IBlockData iblockdata = level.getBlockState(blockposition);
++ InteractionResult enuminteractionresult = InteractionResult.PASS;
++ boolean cancelledBlock = false;
+
+- if (!blockstate.getBlock().isEnabled(level.enabledFeatures())) {
++ if (!iblockdata.getBlock().isEnabled(level.enabledFeatures())) {
+ return InteractionResult.FAIL;
+ } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
+- MenuProvider menuprovider = blockstate.getMenuProvider(level, blockpos);
++ ITileInventory itileinventory = iblockdata.getMenuProvider(level, blockposition);
++ cancelledBlock = !(itileinventory instanceof ITileInventory);
++ }
+
+- if (menuprovider != null) {
+- serverplayer.openMenu(menuprovider);
++ if (player.getCooldowns().isOnCooldown(stack.getItem())) {
++ cancelledBlock = true;
++ }
++
++ PlayerInteractEvent event = CraftEventFactory.callPlayerInteractEvent(player, Action.RIGHT_CLICK_BLOCK, blockposition, hitResult.getDirection(), stack, cancelledBlock, hand, hitResult.getLocation());
++ firedInteract = true;
++ interactResult = event.useItemInHand() == Event.Result.DENY;
++ interactPosition = blockposition.immutable();
++ interactHand = hand;
++ interactItemStack = stack.copy();
++
++ if (event.useInteractedBlock() == Event.Result.DENY) {
++ // If we denied a door from opening, we need to send a correcting update to the client, as it already opened the door.
++ if (iblockdata.getBlock() instanceof DoorBlock) {
++ boolean bottom = iblockdata.getValue(DoorBlock.HALF) == BlockPropertyDoubleBlockHalf.LOWER;
++ player.connection.send(new ClientboundBlockUpdatePacket(level, bottom ? blockposition.above() : blockposition.below()));
++ } else if (iblockdata.getBlock() instanceof CakeBlock) {
++ player.getBukkitEntity().sendHealthUpdate(); // SPIGOT-1341 - reset health for cake
++ } else if (interactItemStack.getItem() instanceof DoubleHighBlockItem) {
++ // send a correcting update to the client, as it already placed the upper half of the bisected item
++ player.connection.send(new ClientboundBlockUpdatePacket(level, blockposition.relative(hitResult.getDirection()).above()));
++
++ // send a correcting update to the client for the block above as well, this because of replaceable blocks (such as grass, sea grass etc)
++ player.connection.send(new ClientboundBlockUpdatePacket(level, blockposition.above()));
++ }
++ player.getBukkitEntity().updateInventory(); // SPIGOT-2867
++ enuminteractionresult = (event.useItemInHand() != Event.Result.ALLOW) ? InteractionResult.SUCCESS : InteractionResult.PASS;
++ } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
++ ITileInventory itileinventory = iblockdata.getMenuProvider(level, blockposition);
++
++ if (itileinventory != null) {
++ player.openMenu(itileinventory);
+ return InteractionResult.SUCCESS;
+ } else {
+ return InteractionResult.PASS;
+ }
+ } else {
+- boolean flag = !serverplayer.getMainHandItem().isEmpty() || !serverplayer.getOffhandItem().isEmpty();
+- boolean flag1 = serverplayer.isSecondaryUseActive() && flag;
+- ItemStack itemstack1 = itemstack.copy();
++ boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
++ boolean flag1 = player.isSecondaryUseActive() && flag;
++ ItemStack itemstack1 = stack.copy();
+
+ if (!flag1) {
+- InteractionResult interactionresult = blockstate.use(level, serverplayer, interactionhand, blockhitresult);
++ enuminteractionresult = iblockdata.use(level, player, hand, hitResult);
+
+- if (interactionresult.consumesAction()) {
+- CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(serverplayer, blockpos, itemstack1);
+- return interactionresult;
++ if (enuminteractionresult.consumesAction()) {
++ CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockposition, itemstack1);
++ return enuminteractionresult;
+ }
+ }
+
+- if (!itemstack.isEmpty() && !serverplayer.getCooldowns().isOnCooldown(itemstack.getItem())) {
+- UseOnContext useoncontext = new UseOnContext(serverplayer, interactionhand, blockhitresult);
+- InteractionResult interactionresult1;
++ if (!stack.isEmpty() && enuminteractionresult != InteractionResult.SUCCESS && !interactResult) { // add !interactResult SPIGOT-764
++ UseOnContext itemactioncontext = new UseOnContext(player, hand, hitResult);
++ InteractionResult enuminteractionresult1;
+
+ if (this.isCreative()) {
+- int i = itemstack.getCount();
++ int i = stack.getCount();
+
+- interactionresult1 = itemstack.useOn(useoncontext);
+- itemstack.setCount(i);
++ enuminteractionresult1 = stack.useOn(itemactioncontext);
++ stack.setCount(i);
+ } else {
+- interactionresult1 = itemstack.useOn(useoncontext);
++ enuminteractionresult1 = stack.useOn(itemactioncontext);
+ }
+
+- if (interactionresult1.consumesAction()) {
+- CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(serverplayer, blockpos, itemstack1);
++ if (enuminteractionresult1.consumesAction()) {
++ CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockposition, itemstack1);
+ }
+
+- return interactionresult1;
+- } else {
+- return InteractionResult.PASS;
++ return enuminteractionresult1;
+ }
+ }
++ return enuminteractionresult;
++ // CraftBukkit end
+ }
+
+- public void setLevel(ServerLevel serverlevel) {
+- this.level = serverlevel;
++ public void setLevel(ServerLevel serverLevel) {
++ this.level = serverLevel;
+ }
+ }