aboutsummaryrefslogtreecommitdiffhomepage
path: root/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch
diff options
context:
space:
mode:
Diffstat (limited to 'patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch')
-rw-r--r--patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch617
1 files changed, 617 insertions, 0 deletions
diff --git a/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch
new file mode 100644
index 0000000000..c2f18c5797
--- /dev/null
+++ b/patch-remap/mache-vineflower/net/minecraft/server/level/ServerPlayerGameMode.java.patch
@@ -0,0 +1,617 @@
+--- a/net/minecraft/server/level/ServerPlayerGameMode.java
++++ b/net/minecraft/server/level/ServerPlayerGameMode.java
+@@ -9,40 +9,65 @@
+ 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 {
++
+ private static final Logger LOGGER = LogUtils.getLogger();
+ protected ServerLevel level;
+ protected final ServerPlayer player;
+- private GameType gameModeForPlayer = GameType.DEFAULT_MODE;
++ private GameType gameModeForPlayer;
+ @Nullable
+ private GameType previousGameModeForPlayer;
+ private boolean isDestroyingBlock;
+ private int destroyProgressStart;
+- private BlockPos destroyPos = BlockPos.ZERO;
++ private BlockPos destroyPos;
+ private int gameTicks;
+ private boolean hasDelayedDestroy;
+- private BlockPos delayedDestroyPos = BlockPos.ZERO;
++ private BlockPos delayedDestroyPos;
+ private int delayedTickStart;
+- private int lastSentState = -1;
++ private int lastSentState;
+
+ public ServerPlayerGameMode(ServerPlayer player) {
++ this.gameModeForPlayer = GameType.DEFAULT_MODE;
++ this.destroyPos = BlockPos.ZERO;
++ this.delayedDestroyPos = BlockPos.ZERO;
++ this.lastSentState = -1;
+ this.player = player;
+ this.level = player.serverLevel();
+ }
+@@ -51,12 +76,16 @@
+ if (gameModeForPlayer == this.gameModeForPlayer) {
+ return false;
+ } else {
++ // 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;
+ }
+@@ -86,59 +115,87 @@
+ }
+
+ public void tick() {
+- this.gameTicks++;
++ this.gameTicks = MinecraftServer.currentTick; // CraftBukkit;
++ IBlockData iblockdata;
++
+ if (this.hasDelayedDestroy) {
+- BlockState 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;
+ this.destroyBlock(this.delayedDestroyPos);
+ }
+ }
+ } else if (this.isDestroyingBlock) {
+- BlockState 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 state, BlockPos pos, int startTick) {
+- int i = this.gameTicks - startTick;
+- float f = state.getDestroyProgress(this.player, this.player.level(), pos) * (float)(i + 1);
+- int i1 = (int)(f * 10.0F);
+- if (i1 != this.lastSentState) {
+- this.level.destroyBlockProgress(this.player.getId(), pos, i1);
+- this.lastSentState = i1;
++ 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(), pos, k);
++ this.lastSentState = k;
+ }
+
+ return f;
+ }
+
+- private void debugLogging(BlockPos pos, boolean flag, int sequence, String message) {
+- }
++ private void debugLogging(BlockPos pos, boolean flag, int sequence, String message) {}
+
+- public void handleBlockBreakAction(BlockPos pos, ServerboundPlayerActionPacket.Action action, Direction face, int maxBuildHeight, int sequence) {
++ 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 {
+- if (action == ServerboundPlayerActionPacket.Action.START_DESTROY_BLOCK) {
++ IBlockData iblockdata;
++
++ 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(pos, sequence, "creative destroy");
+ return;
+@@ -152,13 +209,46 @@
+
+ this.destroyProgressStart = this.gameTicks;
+ float f = 1.0F;
+- BlockState blockState = this.level.getBlockState(pos);
+- if (!blockState.isAir()) {
+- blockState.attack(this.level, pos, this.player);
+- f = blockState.getDestroyProgress(this.player, this.player.level(), pos);
++
++ 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) {
++ 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) {
+@@ -168,17 +258,20 @@
+
+ this.isDestroyingBlock = true;
+ this.destroyPos = pos.immutable();
+- int i = (int)(f * 10.0F);
+- this.level.destroyBlockProgress(this.player.getId(), pos, i);
++ int k = (int) (f * 10.0F);
++
++ this.level.destroyBlockProgress(this.player.getId(), pos, k);
+ this.debugLogging(pos, true, sequence, "actual start of destroying");
+- this.lastSentState = i;
++ this.lastSentState = k;
+ }
+- } else if (action == ServerboundPlayerActionPacket.Action.STOP_DESTROY_BLOCK) {
++ } else if (action == ServerboundPlayerActionPacket.EnumPlayerDigType.STOP_DESTROY_BLOCK) {
+ if (pos.equals(this.destroyPos)) {
+- int i1 = this.gameTicks - this.destroyProgressStart;
+- BlockState blockStatex = this.level.getBlockState(pos);
+- if (!blockStatex.isAir()) {
+- float f1 = blockStatex.getDestroyProgress(this.player, this.player.level(), pos) * (float)(i1 + 1);
++ int l = this.gameTicks - this.destroyProgressStart;
++
++ 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(), pos, -1);
+@@ -196,17 +289,20 @@
+ }
+
+ this.debugLogging(pos, true, sequence, "stopped destroying");
+- } else if (action == ServerboundPlayerActionPacket.Action.ABORT_DESTROY_BLOCK) {
++ } else if (action == ServerboundPlayerActionPacket.EnumPlayerDigType.ABORT_DESTROY_BLOCK) {
+ this.isDestroyingBlock = false;
+ if (!Objects.equals(this.destroyPos, pos)) {
+- LOGGER.warn("Mismatch in destroy block pos: {} {}", 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(pos, true, sequence, "aborted mismatched 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
+ }
++
+ }
+ }
+
+@@ -217,71 +313,149 @@
+ this.player.connection.send(new ClientboundBlockUpdatePacket(pos, this.level.getBlockState(pos)));
+ this.debugLogging(pos, false, sequence, message);
+ }
++
+ }
+
+ public boolean destroyBlock(BlockPos pos) {
+- BlockState blockState = this.level.getBlockState(pos);
+- if (!this.player.getMainHandItem().getItem().canAttackBlock(blockState, this.level, pos, this.player)) {
++ 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 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(pos);
+- 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(pos, blockState, blockState, 3);
++ this.level.sendBlockUpdated(pos, iblockdata, iblockdata, 3);
+ return false;
+ } else if (this.player.blockActionRestricted(this.level, pos, this.gameModeForPlayer)) {
+ return false;
+ } else {
+- BlockState blockState1 = block.playerWillDestroy(this.level, pos, blockState, this.player);
++ // 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, pos, blockState1);
++ block.destroy(this.level, pos, iblockdata1);
+ }
+
+ if (this.isCreative()) {
+- return true;
++ // return true; // CraftBukkit
+ } else {
+- ItemStack mainHandItem = this.player.getMainHandItem();
+- ItemStack itemStack = mainHandItem.copy();
+- boolean hasCorrectToolForDrops = this.player.hasCorrectToolForDrops(blockState1);
+- mainHandItem.mineBlock(this.level, blockState1, pos, this.player);
+- if (flag && hasCorrectToolForDrops) {
+- block.playerDestroy(this.level, this.player, pos, blockState1, blockEntity, itemStack);
++ ItemStack itemstack = this.player.getMainHandItem();
++ ItemStack itemstack1 = itemstack.copy();
++ boolean flag1 = this.player.hasCorrectToolForDrops(iblockdata1);
++
++ 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 player, Level level, ItemStack stack, InteractionHand hand) {
++ public InteractionResult useItem(ServerPlayer player, Level level, ItemStack stack, EnumHand hand) {
+ if (this.gameModeForPlayer == GameType.SPECTATOR) {
+ return InteractionResult.PASS;
+ } else if (player.getCooldowns().isOnCooldown(stack.getItem())) {
+ return InteractionResult.PASS;
+ } else {
+- int count = stack.getCount();
+- int damageValue = stack.getDamageValue();
+- InteractionResultHolder<ItemStack> interactionResultHolder = stack.use(level, player, hand);
+- ItemStack itemStack = interactionResultHolder.getObject();
+- if (itemStack == stack && itemStack.getCount() == count && itemStack.getUseDuration() <= 0 && itemStack.getDamageValue() == damageValue) {
+- return interactionResultHolder.getResult();
+- } else if (interactionResultHolder.getResult() == InteractionResult.FAIL && itemStack.getUseDuration() > 0 && !player.isUsingItem()) {
+- return interactionResultHolder.getResult();
++ int i = stack.getCount();
++ int j = stack.getDamageValue();
++ InteractionResultHolder<ItemStack> interactionresultwrapper = stack.use(level, player, hand);
++ ItemStack itemstack1 = (ItemStack) interactionresultwrapper.getObject();
++
++ 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 (stack != itemStack) {
+- player.setItemInHand(hand, itemStack);
++ if (stack != itemstack1) {
++ player.setItemInHand(hand, itemstack1);
+ }
+
+- if (this.isCreative() && itemStack != ItemStack.EMPTY) {
+- itemStack.setCount(count);
+- if (itemStack.isDamageableItem() && itemStack.getDamageValue() != damageValue) {
+- itemStack.setDamageValue(damageValue);
++ if (this.isCreative() && itemstack1 != ItemStack.EMPTY) {
++ itemstack1.setCount(i);
++ if (itemstack1.isDamageableItem() && itemstack1.getDamageValue() != j) {
++ itemstack1.setDamageValue(j);
+ }
+ }
+
+- if (itemStack.isEmpty()) {
++ if (itemstack1.isEmpty()) {
+ player.setItemInHand(hand, ItemStack.EMPTY);
+ }
+
+@@ -289,20 +463,62 @@
+ player.inventoryMenu.sendAllDataToRemote();
+ }
+
+- return interactionResultHolder.getResult();
++ return interactionresultwrapper.getResult();
+ }
+ }
+ }
+
+- public InteractionResult useItemOn(ServerPlayer player, Level level, ItemStack stack, InteractionHand hand, BlockHitResult hitResult) {
+- BlockPos blockPos = hitResult.getBlockPos();
+- BlockState blockState = level.getBlockState(blockPos);
+- if (!blockState.getBlock().isEnabled(level.enabledFeatures())) {
++ // 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 (!iblockdata.getBlock().isEnabled(level.enabledFeatures())) {
+ return InteractionResult.FAIL;
+ } else if (this.gameModeForPlayer == GameType.SPECTATOR) {
+- MenuProvider menuProvider = blockState.getMenuProvider(level, blockPos);
+- if (menuProvider != null) {
+- player.openMenu(menuProvider);
++ ITileInventory itileinventory = iblockdata.getMenuProvider(level, blockposition);
++ cancelledBlock = !(itileinventory instanceof ITileInventory);
++ }
++
++ 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;
+@@ -310,35 +526,39 @@
+ } else {
+ boolean flag = !player.getMainHandItem().isEmpty() || !player.getOffhandItem().isEmpty();
+ boolean flag1 = player.isSecondaryUseActive() && flag;
+- ItemStack itemStack = stack.copy();
++ ItemStack itemstack1 = stack.copy();
++
+ if (!flag1) {
+- InteractionResult interactionResult = blockState.use(level, player, hand, hitResult);
+- if (interactionResult.consumesAction()) {
+- CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockPos, itemStack);
+- return interactionResult;
++ enuminteractionresult = iblockdata.use(level, player, hand, hitResult);
++
++ if (enuminteractionresult.consumesAction()) {
++ CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockposition, itemstack1);
++ return enuminteractionresult;
+ }
+ }
+
+- if (!stack.isEmpty() && !player.getCooldowns().isOnCooldown(stack.getItem())) {
+- UseOnContext useOnContext = new UseOnContext(player, hand, hitResult);
+- 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 count = stack.getCount();
+- interactionResult1 = stack.useOn(useOnContext);
+- stack.setCount(count);
++ int i = stack.getCount();
++
++ enuminteractionresult1 = stack.useOn(itemactioncontext);
++ stack.setCount(i);
+ } else {
+- interactionResult1 = stack.useOn(useOnContext);
++ enuminteractionresult1 = stack.useOn(itemactioncontext);
+ }
+
+- if (interactionResult1.consumesAction()) {
+- CriteriaTriggers.ITEM_USED_ON_BLOCK.trigger(player, blockPos, itemStack);
++ 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) {