--- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java @@ -30,6 +_,11 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.gameevent.GameEvent; import net.minecraft.world.phys.shapes.CollisionContext; +// CraftBukkit start +import org.bukkit.craftbukkit.block.CraftBlock; +import org.bukkit.craftbukkit.block.data.CraftBlockData; +import org.bukkit.event.block.BlockCanBuildEvent; +// CraftBukkit end public class BlockItem extends Item { @Deprecated @@ -59,6 +_,14 @@ return InteractionResult.FAIL; } else { BlockState placementState = this.getPlacementState(blockPlaceContext); + // CraftBukkit start - special case for handling block placement with water lilies and snow buckets + org.bukkit.block.BlockState bukkitState = null; + if (this instanceof PlaceOnWaterBlockItem || this instanceof SolidBucketItem) { + bukkitState = org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockPlaceContext.getLevel(), blockPlaceContext.getClickedPos()); + } + final org.bukkit.block.BlockState oldBukkitState = bukkitState != null ? bukkitState : org.bukkit.craftbukkit.block.CraftBlockStates.getBlockState(blockPlaceContext.getLevel(), blockPlaceContext.getClickedPos()); // Paper - Reset placed block on exception + // CraftBukkit end + if (placementState == null) { return InteractionResult.FAIL; } else if (!this.placeBlock(blockPlaceContext, placementState)) { @@ -71,15 +_,40 @@ BlockState blockState = level.getBlockState(clickedPos); if (blockState.is(placementState.getBlock())) { blockState = this.updateBlockStateFromTag(clickedPos, level, itemInHand, blockState); + // Paper start - Reset placed block on exception + try { this.updateCustomBlockEntityTag(clickedPos, level, player, itemInHand, blockState); updateBlockEntityComponents(level, clickedPos, itemInHand); + } catch (Exception ex) { + oldBukkitState.update(true, false); + if (player instanceof ServerPlayer serverPlayer) { + org.apache.logging.log4j.LogManager.getLogger().error("Player {} tried placing invalid block", player.getScoreboardName(), ex); + serverPlayer.getBukkitEntity().kickPlayer("Packet processing error"); + return InteractionResult.FAIL; + } + throw ex; // Rethrow exception if not placed by a player + } + // Paper end - Reset placed block on exception blockState.getBlock().setPlacedBy(level, clickedPos, blockState, player, itemInHand); + // CraftBukkit start + if (bukkitState != null) { + org.bukkit.event.block.BlockPlaceEvent placeEvent = org.bukkit.craftbukkit.event.CraftEventFactory.callBlockPlaceEvent((net.minecraft.server.level.ServerLevel) level, player, blockPlaceContext.getHand(), bukkitState, clickedPos.getX(), clickedPos.getY(), clickedPos.getZ()); + if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { + bukkitState.update(true, false); + + // Paper - if the event is called here, the inventory should be updated + player.containerMenu.sendAllDataToRemote(); // SPIGOT-4541 + return InteractionResult.FAIL; + } + } + // CraftBukkit end if (player instanceof ServerPlayer) { CriteriaTriggers.PLACED_BLOCK.trigger((ServerPlayer)player, clickedPos, itemInHand); } } SoundType soundType = blockState.getSoundType(); + if (player == null) // Paper - Fix block place logic; reintroduce this for the dispenser (i.e the shulker) level.playSound( player, clickedPos, @@ -140,8 +_,16 @@ protected boolean canPlace(BlockPlaceContext context, BlockState state) { Player player = context.getPlayer(); CollisionContext collisionContext = player == null ? CollisionContext.empty() : CollisionContext.of(player); - return (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) - && context.getLevel().isUnobstructed(state, context.getClickedPos(), collisionContext); + // CraftBukkit start + Level world = context.getLevel(); // Paper - Cancel hit for vanished players + boolean canBuild = (!this.mustSurvive() || state.canSurvive(world, context.getClickedPos())) && world.checkEntityCollision(state, player, collisionContext, context.getClickedPos(), true); // Paper - Cancel hit for vanished players + org.bukkit.entity.Player bukkitPlayer = (context.getPlayer() instanceof ServerPlayer) ? (org.bukkit.entity.Player) context.getPlayer().getBukkitEntity() : null; + + BlockCanBuildEvent event = new BlockCanBuildEvent(CraftBlock.at(world, context.getClickedPos()), bukkitPlayer, CraftBlockData.fromData(state), canBuild, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand())); // Paper - Expose hand in BlockCanBuildEvent + world.getCraftServer().getPluginManager().callEvent(event); + + return event.isBuildable(); + // CraftBukkit end } protected boolean mustSurvive() { @@ -170,7 +_,7 @@ return false; } - if (!type.onlyOpCanSetNbt() || player != null && player.canUseGameMasterBlocks()) { + if (!type.onlyOpCanSetNbt() || player != null && (player.canUseGameMasterBlocks() || (player.getAbilities().instabuild && player.getBukkitEntity().hasPermission("minecraft.nbt.place")))) { // Spigot - add permission return customData.loadInto(blockEntity, level.registryAccess()); }