--- a/net/minecraft/world/item/BlockItem.java +++ b/net/minecraft/world/item/BlockItem.java @@ -57,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)) { @@ -69,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) { + ((org.bukkit.craftbukkit.block.CraftBlockState) oldBukkitState).revertPlace(); + 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); + if (placeEvent != null && (placeEvent.isCancelled() || !placeEvent.canBuild())) { + ((org.bukkit.craftbukkit.block.CraftBlockState) bukkitState).revertPlace(); + + // 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, @@ -88,7 +_,7 @@ ); level.gameEvent(GameEvent.BLOCK_PLACE, clickedPos, GameEvent.Context.of(player, blockState)); itemInHand.consume(1, player); - return InteractionResult.SUCCESS; + return InteractionResult.SUCCESS.configurePaper(e -> e.placedBlockAt(clickedPos.immutable())); // Paper - track placed block position from block item } } } @@ -137,8 +_,19 @@ protected boolean canPlace(BlockPlaceContext context, BlockState state) { Player player = context.getPlayer(); - return (!this.mustSurvive() || state.canSurvive(context.getLevel(), context.getClickedPos())) - && context.getLevel().isUnobstructed(state, context.getClickedPos(), CollisionContext.placementContext(player)); + // 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.placementContext(player), 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; + + org.bukkit.event.block.BlockCanBuildEvent event = new org.bukkit.event.block.BlockCanBuildEvent( + org.bukkit.craftbukkit.block.CraftBlock.at(world, context.getClickedPos()), bukkitPlayer, + org.bukkit.craftbukkit.block.data.CraftBlockData.fromData(state), canBuild, org.bukkit.craftbukkit.CraftEquipmentSlot.getHand(context.getHand()) + ); + world.getCraftServer().getPluginManager().callEvent(event); + + return event.isBuildable(); + // CraftBukkit end } protected boolean mustSurvive() { @@ -167,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()); }